mirror of
https://github.com/aljazceru/Android-nRF-Toolbox.git
synced 2026-01-08 17:24:31 +01:00
PoC of new approach of handling ble manager
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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 <T> The profile callbacks type.
|
||||
* @see BleManager
|
||||
</T> */
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <T>
|
||||
|
||||
class ConnectingResult<T> : BleManagerResult<T>()
|
||||
class ReadyResult<T> : BleManagerResult<T>()
|
||||
|
||||
data class SuccessResult<T>(val data: T) : BleManagerResult<T>()
|
||||
|
||||
class LinkLossResult<T> : BleManagerResult<T>()
|
||||
class DisconnectedResult<T> : BleManagerResult<T>()
|
||||
class MissingServiceResult<T> : BleManagerResult<T>()
|
||||
|
||||
fun mapToSimpleManagerStatus(): BleManagerStatus {
|
||||
return when (this) {
|
||||
CONNECTING -> BleManagerStatus.CONNECTING
|
||||
OK -> BleManagerStatus.OK
|
||||
DISCONNECTED -> BleManagerStatus.DISCONNECTED
|
||||
LINK_LOSS -> BleManagerStatus.DISCONNECTED
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<T> : ConnectionObserver {
|
||||
|
||||
private val TAG = "BLE-CONNECTION"
|
||||
|
||||
private val _status = MutableStateFlow<BleManagerResult<T>>(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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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<BleManagerResult<CSCData>> {
|
||||
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<BleManagerResult<CSCData>>
|
||||
) : 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<CSCData>()
|
||||
|
||||
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<BatteryLevelResponse>().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() {}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user