diff --git a/lib_service/build.gradle b/lib_service/build.gradle
index 30936d90..7f85c861 100644
--- a/lib_service/build.gradle
+++ b/lib_service/build.gradle
@@ -5,6 +5,7 @@ dependencies {
implementation project(":lib_theme")
implementation libs.nordic.ble.common
+ implementation libs.nordic.ble.ktx
implementation libs.nordic.log
implementation libs.nordic.ui.scanner
diff --git a/lib_service/src/main/java/no/nordicsemi/android/service/BatteryManager.kt b/lib_service/src/main/java/no/nordicsemi/android/service/BatteryManager.kt
index 30c1bb28..eab8e072 100644
--- a/lib_service/src/main/java/no/nordicsemi/android/service/BatteryManager.kt
+++ b/lib_service/src/main/java/no/nordicsemi/android/service/BatteryManager.kt
@@ -1,71 +1,32 @@
package no.nordicsemi.android.service
-import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.content.Context
import android.util.Log
-import androidx.annotation.IntRange
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.cancel
import no.nordicsemi.android.ble.BleManager
-import no.nordicsemi.android.ble.callback.DataReceivedCallback
-import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelDataCallback
-import no.nordicsemi.android.ble.data.Data
-import no.nordicsemi.android.log.LogContract
import java.util.*
-/**
- * The Ble Manager with Battery Service support.
- *
- * @param The profile callbacks type.
- * @see BleManager
- */
-abstract class BatteryManager(context: Context) : BleManager(context) {
+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")
+
+abstract class BatteryManager(
+ context: Context,
+ protected val scope: CoroutineScope,
+) : BleManager(context) {
private val TAG = "BLE-MANAGER"
private var batteryLevelCharacteristic: BluetoothGattCharacteristic? = null
- private val batteryLevelDataCallback: DataReceivedCallback =
- object : BatteryLevelDataCallback() {
- override fun onBatteryLevelChanged(
- device: BluetoothDevice,
- @IntRange(from = 0, to = 100) batteryLevel: Int
- ) {
- log(LogContract.Log.Level.APPLICATION, "Battery Level received: $batteryLevel%")
- onBatteryLevelChanged(batteryLevel)
- }
- override fun onInvalidDataReceived(device: BluetoothDevice, data: Data) {
- log(Log.WARN, "Invalid Battery Level data received: $data")
- }
- }
- protected abstract fun onBatteryLevelChanged(batteryLevel: Int)
-
- fun readBatteryLevelCharacteristic() {
- if (isConnected) {
- readCharacteristic(batteryLevelCharacteristic)
- .with(batteryLevelDataCallback)
- .fail { device: BluetoothDevice?, status: Int ->
- log(Log.WARN, "Battery Level characteristic not found")
- }
- .enqueue()
- }
- }
fun enableBatteryLevelCharacteristicNotifications() {
if (isConnected) {
- // If the Battery Level characteristic is null, the request will be ignored
- setNotificationCallback(batteryLevelCharacteristic)
- .with(batteryLevelDataCallback)
- enableNotifications(batteryLevelCharacteristic)
- .done { device: BluetoothDevice? ->
- log(Log.INFO, "Battery Level notifications enabled")
- }
- .fail { device: BluetoothDevice?, status: Int ->
- log(Log.WARN, "Battery Level characteristic not found")
- }
- .enqueue()
+
}
}
@@ -76,7 +37,6 @@ abstract class BatteryManager(context: Context) : BleManager(context) {
protected abstract inner class BatteryManagerGattCallback : BleManagerGattCallback() {
override fun initialize() {
- readBatteryLevelCharacteristic()
enableBatteryLevelCharacteristicNotifications()
}
@@ -88,18 +48,12 @@ abstract class BatteryManager(context: Context) : BleManager(context) {
return batteryLevelCharacteristic != null
}
- override fun onDeviceDisconnected() {
+ override fun onServicesInvalidated() {
batteryLevelCharacteristic = null
- onBatteryLevelChanged(0)
}
}
- companion object {
- /** Battery Service UUID. */
- private val BATTERY_SERVICE_UUID = UUID.fromString("0000180F-0000-1000-8000-00805f9b34fb")
-
- /** Battery Level characteristic UUID. */
- private val BATTERY_LEVEL_CHARACTERISTIC_UUID =
- UUID.fromString("00002A19-0000-1000-8000-00805f9b34fb")
+ fun releaseScope() {
+ scope.cancel()
}
}
diff --git a/lib_service/src/main/java/no/nordicsemi/android/service/BleManagerStatus.kt b/lib_service/src/main/java/no/nordicsemi/android/service/BleManagerStatus.kt
index a546f678..7186abe9 100644
--- a/lib_service/src/main/java/no/nordicsemi/android/service/BleManagerStatus.kt
+++ b/lib_service/src/main/java/no/nordicsemi/android/service/BleManagerStatus.kt
@@ -1,18 +1,17 @@
package no.nordicsemi.android.service
enum class BleManagerStatus {
- CONNECTING, OK, DISCONNECTED
+ CONNECTING, OK, LINK_LOSS, DISCONNECTED, MISSING_SERVICE
}
-enum class BleServiceStatus {
- CONNECTING, OK, DISCONNECTED, LINK_LOSS;
+sealed class BleManagerResult
+
+class ConnectingResult : BleManagerResult()
+class ReadyResult : BleManagerResult()
+
+data class SuccessResult(val data: T) : BleManagerResult()
+
+class LinkLossResult : BleManagerResult()
+class DisconnectedResult : BleManagerResult()
+class MissingServiceResult : BleManagerResult()
- fun mapToSimpleManagerStatus(): BleManagerStatus {
- return when (this) {
- CONNECTING -> BleManagerStatus.CONNECTING
- OK -> BleManagerStatus.OK
- DISCONNECTED -> BleManagerStatus.DISCONNECTED
- LINK_LOSS -> BleManagerStatus.DISCONNECTED
- }
- }
-}
diff --git a/lib_service/src/main/java/no/nordicsemi/android/service/BleProfileService.kt b/lib_service/src/main/java/no/nordicsemi/android/service/BleProfileService.kt
index 15760d1e..8098fa5c 100644
--- a/lib_service/src/main/java/no/nordicsemi/android/service/BleProfileService.kt
+++ b/lib_service/src/main/java/no/nordicsemi/android/service/BleProfileService.kt
@@ -22,19 +22,14 @@
package no.nordicsemi.android.service
import android.app.Service
-import android.bluetooth.BluetoothDevice
import android.content.Intent
import android.os.Handler
import android.os.IBinder
-import android.util.Log
import android.widget.Toast
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
import no.nordicsemi.android.ble.BleManager
-import no.nordicsemi.android.ble.observer.ConnectionObserver
import no.nordicsemi.android.log.ILogSession
import no.nordicsemi.android.log.Logger
import no.nordicsemi.ui.scanner.DiscoveredBluetoothDevice
@@ -46,9 +41,6 @@ abstract class BleProfileService : Service() {
protected abstract val manager: BleManager
- private val _status = MutableStateFlow(BleServiceStatus.CONNECTING)
- val status = _status.asStateFlow()
-
/**
* Returns a handler that is created in onCreate().
* The handler may be used to postpone execution of some operations or to run them in UI thread.
@@ -69,27 +61,6 @@ abstract class BleProfileService : Service() {
override fun onCreate() {
super.onCreate()
handler = Handler()
-
- manager.setConnectionObserver(object : ConnectionObserverAdapter() {
- override fun onDeviceConnected(device: BluetoothDevice) {
- super.onDeviceConnected(device)
- _status.value = BleServiceStatus.OK
- }
-
- override fun onDeviceFailedToConnect(device: BluetoothDevice, reason: Int) {
- super.onDeviceFailedToConnect(device, reason)
- _status.value = BleServiceStatus.DISCONNECTED
- }
-
- override fun onDeviceDisconnected(device: BluetoothDevice, reason: Int) {
- super.onDeviceDisconnected(device, reason)
- if (reason == ConnectionObserver.REASON_LINK_LOSS) {
- _status.value = BleServiceStatus.LINK_LOSS
- } else {
- _status.value = BleServiceStatus.DISCONNECTED
- }
- }
- })
}
protected fun stopIfDisconnected(status: BleManagerStatus) {
diff --git a/lib_service/src/main/java/no/nordicsemi/android/service/ConnectionObserverAdapter.kt b/lib_service/src/main/java/no/nordicsemi/android/service/ConnectionObserverAdapter.kt
index ea556c75..074f85cb 100644
--- a/lib_service/src/main/java/no/nordicsemi/android/service/ConnectionObserverAdapter.kt
+++ b/lib_service/src/main/java/no/nordicsemi/android/service/ConnectionObserverAdapter.kt
@@ -2,22 +2,29 @@ package no.nordicsemi.android.service
import android.bluetooth.BluetoothDevice
import android.util.Log
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
import no.nordicsemi.android.ble.observer.ConnectionObserver
-abstract class ConnectionObserverAdapter : ConnectionObserver {
+class ConnectionObserverAdapter : ConnectionObserver {
private val TAG = "BLE-CONNECTION"
+ private val _status = MutableStateFlow>(ConnectingResult())
+ val status = _status.asStateFlow()
+
override fun onDeviceConnecting(device: BluetoothDevice) {
Log.d(TAG, "onDeviceConnecting()")
}
override fun onDeviceConnected(device: BluetoothDevice) {
Log.d(TAG, "onDeviceConnected()")
+ _status.value = ReadyResult()
}
override fun onDeviceFailedToConnect(device: BluetoothDevice, reason: Int) {
Log.d(TAG, "onDeviceFailedToConnect()")
+ _status.value = DisconnectedResult()
}
override fun onDeviceReady(device: BluetoothDevice) {
@@ -30,5 +37,14 @@ abstract class ConnectionObserverAdapter : ConnectionObserver {
override fun onDeviceDisconnected(device: BluetoothDevice, reason: Int) {
Log.d(TAG, "onDeviceDisconnected()")
+ _status.value = when (reason) {
+ ConnectionObserver.REASON_NOT_SUPPORTED -> MissingServiceResult()
+ ConnectionObserver.REASON_LINK_LOSS -> LinkLossResult()
+ else -> DisconnectedResult()
+ }
+ }
+
+ fun setValue(value: T) {
+ _status.tryEmit(SuccessResult(value))
}
}
diff --git a/profile_bps/src/main/java/no/nordicsemi/android/bps/repository/BPSManager.kt b/profile_bps/src/main/java/no/nordicsemi/android/bps/repository/BPSManager.kt
index 95de9f53..b7b2623f 100644
--- a/profile_bps/src/main/java/no/nordicsemi/android/bps/repository/BPSManager.kt
+++ b/profile_bps/src/main/java/no/nordicsemi/android/bps/repository/BPSManager.kt
@@ -38,13 +38,12 @@ import no.nordicsemi.android.ble.common.callback.bps.IntermediateCuffPressureRes
import no.nordicsemi.android.ble.ktx.asValidResponseFlow
import no.nordicsemi.android.ble.ktx.suspend
import no.nordicsemi.android.bps.data.BPSRepository
-import no.nordicsemi.android.log.Logger
import no.nordicsemi.android.service.BatteryManager
import no.nordicsemi.android.service.CloseableCoroutineScope
import java.util.*
import javax.inject.Inject
-val BPS_SERVICE_UUID = UUID.fromString("00001810-0000-1000-8000-00805f9b34fb")
+val BPS_SERVICE_UUID: UUID = UUID.fromString("00001810-0000-1000-8000-00805f9b34fb")
private val BPM_CHARACTERISTIC_UUID = UUID.fromString("00002A35-0000-1000-8000-00805f9b34fb")
@@ -54,9 +53,7 @@ private val ICP_CHARACTERISTIC_UUID = UUID.fromString("00002A36-0000-1000-8000-0
internal class BPSManager @Inject constructor(
@ApplicationContext context: Context,
private val dataHolder: BPSRepository
-) : BatteryManager(context) {
-
- private val scope = CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
+) : BatteryManager(context, CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)) {
private var bpmCharacteristic: BluetoothGattCharacteristic? = null
private var icpCharacteristic: BluetoothGattCharacteristic? = null
@@ -134,8 +131,4 @@ internal class BPSManager @Inject constructor(
override fun getGattCallback(): BleManagerGattCallback {
return BloodPressureManagerGattCallback()
}
-
- fun release() {
- scope.close()
- }
}
diff --git a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMManager.kt b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMManager.kt
index abaafb07..8feec5f3 100644
--- a/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMManager.kt
+++ b/profile_cgms/src/main/java/no/nordicsemi/android/cgms/repository/CGMManager.kt
@@ -49,7 +49,7 @@ import no.nordicsemi.android.cgms.data.RequestStatus
import no.nordicsemi.android.service.BatteryManager
import java.util.*
-val CGMS_SERVICE_UUID = UUID.fromString("0000181F-0000-1000-8000-00805f9b34fb")
+val CGMS_SERVICE_UUID: UUID = UUID.fromString("0000181F-0000-1000-8000-00805f9b34fb")
private val CGM_STATUS_UUID = UUID.fromString("00002AA9-0000-1000-8000-00805f9b34fb")
private val CGM_FEATURE_UUID = UUID.fromString("00002AA8-0000-1000-8000-00805f9b34fb")
private val CGM_MEASUREMENT_UUID = UUID.fromString("00002AA7-0000-1000-8000-00805f9b34fb")
@@ -59,9 +59,9 @@ private val RACP_UUID = UUID.fromString("00002A52-0000-1000-8000-00805f9b34fb")
internal class CGMManager(
context: Context,
- private val scope: CoroutineScope,
+ scope: CoroutineScope,
private val repository: CGMRepository
-) : BatteryManager(context) {
+) : BatteryManager(context, scope) {
private var cgmStatusCharacteristic: BluetoothGattCharacteristic? = null
private var cgmFeatureCharacteristic: BluetoothGattCharacteristic? = null
@@ -248,7 +248,7 @@ internal class CGMManager(
}
}
- fun clear() {
+ private fun clear() {
records.clear()
}
diff --git a/profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCRepository.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCRepository.kt
index c451cd5f..2e7c8878 100644
--- a/profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCRepository.kt
+++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/data/CSCRepository.kt
@@ -1,5 +1,6 @@
package no.nordicsemi.android.csc.data
+import dagger.hilt.android.scopes.ServiceScoped
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.*
import no.nordicsemi.android.csc.view.SpeedUnit
@@ -7,7 +8,7 @@ import no.nordicsemi.android.service.BleManagerStatus
import javax.inject.Inject
import javax.inject.Singleton
-@Singleton
+@ServiceScoped
internal class CSCRepository @Inject constructor() {
private val _data = MutableStateFlow(CSCData())
diff --git a/profile_csc/src/main/java/no/nordicsemi/android/csc/repository/CSCManager.kt b/profile_csc/src/main/java/no/nordicsemi/android/csc/repository/CSCManager.kt
index aac475c0..82165cd4 100644
--- a/profile_csc/src/main/java/no/nordicsemi/android/csc/repository/CSCManager.kt
+++ b/profile_csc/src/main/java/no/nordicsemi/android/csc/repository/CSCManager.kt
@@ -21,43 +21,92 @@
*/
package no.nordicsemi.android.csc.repository
+import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.content.Context
import android.util.Log
+import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.channels.SendChannel
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
+import no.nordicsemi.android.ble.common.callback.battery.BatteryLevelResponse
import no.nordicsemi.android.ble.common.callback.csc.CyclingSpeedAndCadenceMeasurementResponse
import no.nordicsemi.android.ble.ktx.asValidResponseFlow
import no.nordicsemi.android.ble.ktx.suspend
-import no.nordicsemi.android.csc.data.CSCRepository
+import no.nordicsemi.android.csc.data.CSCData
import no.nordicsemi.android.csc.data.WheelSize
-import no.nordicsemi.android.service.BatteryManager
+import no.nordicsemi.android.service.*
import java.util.*
+import javax.inject.Inject
val CSC_SERVICE_UUID: UUID = UUID.fromString("00001816-0000-1000-8000-00805f9b34fb")
private val CSC_MEASUREMENT_CHARACTERISTIC_UUID = UUID.fromString("00002A5B-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")
+
+internal class CSCRepo @Inject constructor(
+ @ApplicationContext
+ private val context: Context,
+) {
+
+ suspend fun downloadData(device: BluetoothDevice) = callbackFlow> {
+ val scope = CoroutineScope(coroutineContext)
+ val manager = CSCManager(context, scope)
+
+ manager.dataHolder.status.onEach {
+ trySend(it)
+ }.launchIn(scope)
+
+ scope.launch {
+ manager.connect(device)
+ .useAutoConnect(false)
+ .retry(3, 100)
+ .suspend()
+ }
+
+ awaitClose {
+ scope.launch {
+ manager.disconnect().suspend()
+ }
+ scope.cancel()
+ }
+ }
+}
+
internal class CSCManager(
context: Context,
- private val scope: CoroutineScope,
- private val repository: CSCRepository
-) : BatteryManager(context) {
+ scope: CoroutineScope,
+// private val channel: SendChannel>
+) : BatteryManager(context, scope) {
+ private var batteryLevelCharacteristic: BluetoothGattCharacteristic? = null
private var cscMeasurementCharacteristic: BluetoothGattCharacteristic? = null
private var wheelSize: WheelSize = WheelSize()
private var previousResponse: CyclingSpeedAndCadenceMeasurementResponse? = null
- private val exceptionHandler = CoroutineExceptionHandler { context, t->
+ private val data = MutableStateFlow(CSCData())
+ val dataHolder = ConnectionObserverAdapter()
+
+ private val exceptionHandler = CoroutineExceptionHandler { context, t ->
Log.e("COROUTINE-EXCEPTION", "Uncaught exception", t)
}
- override fun onBatteryLevelChanged(batteryLevel: Int) {
- repository.setBatteryLevel(batteryLevel)
+ init {
+ setConnectionObserver(dataHolder)
+
+ data.onEach {
+ dataHolder.setValue(it)
+ }.launchIn(scope)
}
override fun getGattCallback(): BatteryManagerGattCallback {
@@ -79,6 +128,9 @@ internal class CSCManager(
val totalDistance = it.getTotalDistance(wheelSize.value.toFloat())
val distance = it.getDistance(wheelCircumference, previousResponse)
val speed = it.getSpeed(wheelCircumference, previousResponse)
+
+ //todo
+ data.value.copy(totalDistance, )
repository.setNewDistance(totalDistance, distance, speed, wheelSize)
val crankCadence = it.getCrankCadence(previousResponse)
@@ -92,21 +144,29 @@ internal class CSCManager(
scope.launch(exceptionHandler) {
enableNotifications(cscMeasurementCharacteristic).suspend()
}
+
+ setNotificationCallback(batteryLevelCharacteristic).asValidResponseFlow().onEach {
+ data.value = data.value.copy(batteryLevel = it.batteryLevel)
+ }.launchIn(scope)
+
+ scope.launch {
+ enableNotifications(batteryLevelCharacteristic).suspend()
+ }
}
public override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean {
val service = gatt.getService(CSC_SERVICE_UUID)
if (service != null) {
cscMeasurementCharacteristic = service.getCharacteristic(CSC_MEASUREMENT_CHARACTERISTIC_UUID)
+ batteryLevelCharacteristic = service.getCharacteristic(BATTERY_LEVEL_CHARACTERISTIC_UUID)
}
return cscMeasurementCharacteristic != null
}
- override fun onDeviceDisconnected() {
- super.onDeviceDisconnected()
+ override fun onServicesInvalidated() {
+ super.onServicesInvalidated()
cscMeasurementCharacteristic = null
+ batteryLevelCharacteristic = null
}
-
- override fun onServicesInvalidated() {}
}
}