Add Link Loss support for prx

This commit is contained in:
Sylwester Zieliński
2022-02-01 14:47:58 +01:00
parent 720259f137
commit 05d48ed03e
13 changed files with 139 additions and 37 deletions

View File

@@ -3,3 +3,16 @@ package no.nordicsemi.android.service
enum class BleManagerStatus {
CONNECTING, OK, DISCONNECTED
}
enum class BleServiceStatus {
CONNECTING, OK, DISCONNECTED, LINK_LOSS;
fun mapToSimpleManagerStatus(): BleManagerStatus {
return when (this) {
CONNECTING -> BleManagerStatus.CONNECTING
OK -> BleManagerStatus.OK
DISCONNECTED -> BleManagerStatus.DISCONNECTED
LINK_LOSS -> BleManagerStatus.DISCONNECTED
}
}
}

View File

@@ -33,6 +33,7 @@ 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
@@ -44,7 +45,7 @@ abstract class BleProfileService : Service() {
protected abstract val manager: BleManager
private val _status = MutableStateFlow(BleManagerStatus.CONNECTING)
private val _status = MutableStateFlow(BleServiceStatus.CONNECTING)
val status = _status.asStateFlow()
/**
@@ -71,20 +72,22 @@ abstract class BleProfileService : Service() {
manager.setConnectionObserver(object : ConnectionObserverAdapter() {
override fun onDeviceConnected(device: BluetoothDevice) {
super.onDeviceConnected(device)
_status.value = BleManagerStatus.OK
_status.value = BleServiceStatus.OK
}
override fun onDeviceFailedToConnect(device: BluetoothDevice, reason: Int) {
super.onDeviceFailedToConnect(device, reason)
_status.value = BleManagerStatus.DISCONNECTED
_status.value = BleServiceStatus.DISCONNECTED
stopSelf()
scope.close()
}
override fun onDeviceDisconnected(device: BluetoothDevice, reason: Int) {
super.onDeviceDisconnected(device, reason)
_status.value = BleManagerStatus.DISCONNECTED
scope.close()
if (reason == ConnectionObserver.REASON_LINK_LOSS) {
_status.value = BleServiceStatus.LINK_LOSS
} else {
_status.value = BleServiceStatus.DISCONNECTED
}
}
})
}
@@ -98,7 +101,7 @@ abstract class BleProfileService : Service() {
*
* @return true to use autoConnect feature, false (default) otherwise.
*/
private fun shouldAutoConnect(): Boolean {
protected open fun shouldAutoConnect(): Boolean {
return false
}

View File

@@ -5,6 +5,7 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import no.nordicsemi.android.cgms.data.CGMRepository
import no.nordicsemi.android.cgms.data.CGMServiceCommand
import no.nordicsemi.android.service.BleManagerStatus
import no.nordicsemi.android.service.ForegroundBleService
import no.nordicsemi.android.utils.exhaustive
import javax.inject.Inject
@@ -21,7 +22,11 @@ internal class CGMService : ForegroundBleService() {
super.onCreate()
status.onEach {
repository.setNewStatus(it)
val status = it.mapToSimpleManagerStatus()
repository.setNewStatus(status)
if (status == BleManagerStatus.DISCONNECTED) {
scope.close()
}
}.launchIn(scope)
repository.command.onEach {

View File

@@ -1,12 +1,12 @@
package no.nordicsemi.android.csc.repository
import android.util.Log
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import no.nordicsemi.android.csc.data.CSCRepository
import no.nordicsemi.android.csc.data.DisconnectCommand
import no.nordicsemi.android.csc.data.SetWheelSizeCommand
import no.nordicsemi.android.service.BleManagerStatus
import no.nordicsemi.android.service.ForegroundBleService
import no.nordicsemi.android.utils.exhaustive
import javax.inject.Inject
@@ -23,7 +23,11 @@ internal class CSCService : ForegroundBleService() {
super.onCreate()
status.onEach {
repository.setNewStatus(it)
val status = it.mapToSimpleManagerStatus()
repository.setNewStatus(status)
if (status == BleManagerStatus.DISCONNECTED) {
scope.close()
}
}.launchIn(scope)
repository.command.onEach {

View File

@@ -4,7 +4,10 @@ import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import no.nordicsemi.android.hrs.data.HRSRepository
import no.nordicsemi.android.service.BleManagerStatus
import no.nordicsemi.android.service.BleServiceStatus
import no.nordicsemi.android.service.ForegroundBleService
import no.nordicsemi.android.utils.exhaustive
import javax.inject.Inject
@AndroidEntryPoint
@@ -19,7 +22,11 @@ internal class HRSService : ForegroundBleService() {
super.onCreate()
status.onEach {
repository.setNewStatus(it)
val status = it.mapToSimpleManagerStatus()
repository.setNewStatus(status)
if (status == BleManagerStatus.DISCONNECTED) {
scope.close()
}
}.launchIn(scope)
repository.command.onEach {

View File

@@ -4,7 +4,10 @@ import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import no.nordicsemi.android.hts.data.HTSRepository
import no.nordicsemi.android.service.BleManagerStatus
import no.nordicsemi.android.service.BleServiceStatus
import no.nordicsemi.android.service.ForegroundBleService
import no.nordicsemi.android.utils.exhaustive
import javax.inject.Inject
@AndroidEntryPoint
@@ -19,7 +22,11 @@ internal class HTSService : ForegroundBleService() {
super.onCreate()
status.onEach {
repository.setNewStatus(it)
val status = it.mapToSimpleManagerStatus()
repository.setNewStatus(status)
if (status == BleManagerStatus.DISCONNECTED) {
scope.close()
}
}.launchIn(scope)
repository.command.onEach {

View File

@@ -3,7 +3,8 @@ package no.nordicsemi.android.prx.data
internal data class PRXData(
val batteryLevel: Int = 0,
val localAlarmLevel: AlarmLevel = AlarmLevel.NONE,
val isRemoteAlarm: Boolean = false
val isRemoteAlarm: Boolean = false,
val linkLossAlarmLevel: AlarmLevel = AlarmLevel.HIGH
)
internal enum class AlarmLevel(val value: Int) {

View File

@@ -27,16 +27,22 @@ internal class PRXRepository @Inject constructor() {
_data.tryEmit(_data.value.copy(localAlarmLevel = alarmLevel))
}
fun setLocalAlarmLevel(alarmLevel: AlarmLevel) {
_data.tryEmit(_data.value.copy(localAlarmLevel = alarmLevel))
}
fun setLinkLossLevel(value: Int) {
val alarmLevel = AlarmLevel.create(value)
_data.tryEmit(_data.value.copy(linkLossAlarmLevel = alarmLevel))
}
fun setRemoteAlarmLevel(isOn: Boolean) {
_data.tryEmit(_data.value.copy(isRemoteAlarm = isOn))
}
fun invokeCommand(command: PRXCommand) {
if (_command.subscriptionCount.value > 0) {
_command.tryEmit(command)
} else {
_status.tryEmit(BleManagerStatus.DISCONNECTED)
}
_command.tryEmit(command)
_status.tryEmit(BleManagerStatus.DISCONNECTED)
}
fun setNewStatus(status: BleManagerStatus) {

View File

@@ -7,10 +7,12 @@ import android.media.RingtoneManager
import android.util.Log
import androidx.lifecycle.LifecycleService
import dagger.hilt.android.qualifiers.ApplicationContext
import no.nordicsemi.android.prx.data.AlarmLevel
import java.io.IOException
import java.lang.Exception
import javax.inject.Inject
class AlarmHandler @Inject constructor(
internal class AlarmHandler @Inject constructor(
@ApplicationContext
private val context: Context
) {
@@ -34,28 +36,39 @@ class AlarmHandler @Inject constructor(
}
}
fun playAlarm() {
fun playAlarm(alarmLevel: AlarmLevel) {
val am = context.getSystemService(LifecycleService.AUDIO_SERVICE) as AudioManager
originalVolume = am.getStreamVolume(AudioManager.STREAM_ALARM)
val soundLevel = when (alarmLevel) {
AlarmLevel.NONE -> 0
AlarmLevel.MEDIUM -> originalVolume / 2
AlarmLevel.HIGH -> originalVolume
}
am.setStreamVolume(
AudioManager.STREAM_ALARM,
am.getStreamMaxVolume(AudioManager.STREAM_ALARM),
soundLevel,
AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE
)
try {
mediaPlayer.prepare()
mediaPlayer.start()
} catch (e: IOException) {
} catch (e: Exception) {
Log.e(TAG, "Prepare Alarm failed: ", e)
}
}
fun pauseAlarm() {
if (mediaPlayer.isPlaying) {
mediaPlayer.stop()
// Restore original volume
val am = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
am.setStreamVolume(AudioManager.STREAM_ALARM, originalVolume, 0)
try {
if (mediaPlayer.isPlaying) {
mediaPlayer.stop()
// Restore original volume
val am = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
am.setStreamVolume(AudioManager.STREAM_ALARM, originalVolume, 0)
}
} catch (e: Exception) {
Log.e(TAG, "Prepare Alarm failed: ", e)
}
}

View File

@@ -55,6 +55,7 @@ internal class PRXManager(
// Server characteristics.
private var localAlertLevelCharacteristic: BluetoothGattCharacteristic? = null
private var linkLossServerCharacteristic: BluetoothGattCharacteristic? = null
/**
* Returns true if the alert has been enabled on the proximity tag, false otherwise.
*/
@@ -77,8 +78,16 @@ internal class PRXManager(
dataHolder.setLocalAlarmLevel(level)
}
})
setWriteCallback(linkLossServerCharacteristic)
.with(object : AlertLevelDataCallback() {
override fun onAlertLevelChanged(device: BluetoothDevice, level: Int) {
dataHolder.setLinkLossLevel(level)
}
})
// After connection, set the Link Loss behaviour on the tag.
writeCharacteristic(linkLossCharacteristic, AlertLevelData.highAlert())
writeCharacteristic(linkLossCharacteristic, AlertLevelData.highAlert(), BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
.done { device: BluetoothDevice? ->
log(
Log.INFO,
@@ -97,9 +106,11 @@ internal class PRXManager(
override fun onServerReady(server: BluetoothGattServer) {
val immediateAlertService = server.getService(PRX_SERVICE_UUID)
if (immediateAlertService != null) {
localAlertLevelCharacteristic = immediateAlertService.getCharacteristic(
ALERT_LEVEL_CHARACTERISTIC_UUID
)
localAlertLevelCharacteristic = immediateAlertService.getCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID)
}
val linkLossService = server.getService(LINK_LOSS_SERVICE_UUID)
if (linkLossService != null) {
linkLossServerCharacteristic = linkLossService.getCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID)
}
}
@@ -108,8 +119,7 @@ internal class PRXManager(
override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean {
val llService = gatt.getService(LINK_LOSS_SERVICE_UUID)
if (llService != null) {
linkLossCharacteristic =
llService.getCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID)
linkLossCharacteristic = llService.getCharacteristic(ALERT_LEVEL_CHARACTERISTIC_UUID)
}
return linkLossCharacteristic != null
}
@@ -130,6 +140,7 @@ internal class PRXManager(
alertLevelCharacteristic = null
linkLossCharacteristic = null
localAlertLevelCharacteristic = null
linkLossServerCharacteristic = null
// Reset the alert flag
isAlertEnabled = false
}

View File

@@ -1,5 +1,6 @@
package no.nordicsemi.android.prx.repository
import android.util.Log
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@@ -8,6 +9,8 @@ import no.nordicsemi.android.prx.data.DisableAlarm
import no.nordicsemi.android.prx.data.Disconnect
import no.nordicsemi.android.prx.data.EnableAlarm
import no.nordicsemi.android.prx.data.PRXRepository
import no.nordicsemi.android.service.BleManagerStatus
import no.nordicsemi.android.service.BleServiceStatus
import no.nordicsemi.android.service.ForegroundBleService
import no.nordicsemi.android.utils.exhaustive
import javax.inject.Inject
@@ -35,7 +38,20 @@ internal class PRXService : ForegroundBleService() {
serverManager.open()
status.onEach {
repository.setNewStatus(it)
val bleStatus = when (it) {
BleServiceStatus.CONNECTING -> BleManagerStatus.CONNECTING
BleServiceStatus.OK -> BleManagerStatus.OK
BleServiceStatus.DISCONNECTED -> {
scope.close()
BleManagerStatus.DISCONNECTED
}
BleServiceStatus.LINK_LOSS -> null
}.exhaustive
bleStatus?.let { repository.setNewStatus(it) }
if (BleServiceStatus.LINK_LOSS == it) {
repository.setLocalAlarmLevel(repository.data.value.linkLossAlarmLevel)
}
}.launchIn(scope)
repository.command.onEach {
@@ -48,13 +64,17 @@ internal class PRXService : ForegroundBleService() {
repository.data.onEach {
if (it.localAlarmLevel != AlarmLevel.NONE) {
alarmHandler.playAlarm()
alarmHandler.playAlarm(it.localAlarmLevel)
} else {
alarmHandler.pauseAlarm()
}
}.launchIn(scope)
}
override fun shouldAutoConnect(): Boolean {
return true
}
override fun onDestroy() {
super.onDestroy()
alarmHandler.releaseAlarm()

View File

@@ -4,7 +4,10 @@ import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import no.nordicsemi.android.rscs.data.RSCSRepository
import no.nordicsemi.android.service.BleManagerStatus
import no.nordicsemi.android.service.BleServiceStatus
import no.nordicsemi.android.service.ForegroundBleService
import no.nordicsemi.android.utils.exhaustive
import javax.inject.Inject
@AndroidEntryPoint
@@ -19,7 +22,11 @@ internal class RSCSService : ForegroundBleService() {
super.onCreate()
status.onEach {
repository.setNewStatus(it)
val status = it.mapToSimpleManagerStatus()
repository.setNewStatus(status)
if (status == BleManagerStatus.DISCONNECTED) {
scope.close()
}
}.launchIn(scope)
repository.command.onEach {

View File

@@ -3,6 +3,7 @@ package no.nordicsemi.android.uart.repository
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import no.nordicsemi.android.service.BleManagerStatus
import no.nordicsemi.android.service.ForegroundBleService
import no.nordicsemi.android.uart.data.DisconnectCommand
import no.nordicsemi.android.uart.data.SendTextCommand
@@ -22,7 +23,11 @@ internal class UARTService : ForegroundBleService() {
super.onCreate()
status.onEach {
repository.setNewStatus(it)
val status = it.mapToSimpleManagerStatus()
repository.setNewStatus(status)
if (status == BleManagerStatus.DISCONNECTED) {
scope.close()
}
}.launchIn(scope)
repository.command.onEach {