From d16a908d6b2bdd0335386c616ee23c121c0f616a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sylwester=20Zieli=C5=84ski?= Date: Fri, 4 Feb 2022 17:16:38 +0100 Subject: [PATCH] PoC of new approach of handling ble manager --- lib_service/build.gradle | 1 + .../android/service/BatteryManager.kt | 72 +++------------- .../android/service/BleManagerStatus.kt | 23 +++-- .../android/service/BleProfileService.kt | 29 ------- .../service/ConnectionObserverAdapter.kt | 18 +++- .../android/bps/repository/BPSManager.kt | 11 +-- .../android/cgms/repository/CGMManager.kt | 8 +- .../android/csc/data/CSCRepository.kt | 3 +- .../android/csc/repository/CSCManager.kt | 84 ++++++++++++++++--- 9 files changed, 122 insertions(+), 127 deletions(-) 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() {} } }