mirror of
https://github.com/aljazceru/Android-nRF-Toolbox.git
synced 2025-12-19 23:44:24 +01:00
Remove CloseableViewModel
This commit is contained in:
@@ -1,13 +0,0 @@
|
|||||||
package no.nordicsemi.android.theme.viewmodel
|
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
|
|
||||||
abstract class CloseableViewModel : ViewModel() {
|
|
||||||
|
|
||||||
var isActive = MutableStateFlow(true)
|
|
||||||
|
|
||||||
protected fun finish() {
|
|
||||||
isActive.tryEmit(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,9 @@ package no.nordicsemi.android.bps.data
|
|||||||
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import no.nordicsemi.android.ble.common.profile.bp.BloodPressureTypes
|
import no.nordicsemi.android.ble.common.profile.bp.BloodPressureTypes
|
||||||
|
import no.nordicsemi.android.service.BleManagerStatus
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@@ -13,6 +15,9 @@ internal class BPSRepository @Inject constructor() {
|
|||||||
private val _data = MutableStateFlow(BPSData())
|
private val _data = MutableStateFlow(BPSData())
|
||||||
val data: StateFlow<BPSData> = _data
|
val data: StateFlow<BPSData> = _data
|
||||||
|
|
||||||
|
private val _status = MutableStateFlow(BleManagerStatus.CONNECTING)
|
||||||
|
val status = _status.asStateFlow()
|
||||||
|
|
||||||
fun setIntermediateCuffPressure(
|
fun setIntermediateCuffPressure(
|
||||||
cuffPressure: Float,
|
cuffPressure: Float,
|
||||||
unit: Int,
|
unit: Int,
|
||||||
@@ -60,4 +65,8 @@ internal class BPSRepository @Inject constructor() {
|
|||||||
fun clear() {
|
fun clear() {
|
||||||
_data.tryEmit(BPSData())
|
_data.tryEmit(BPSData())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setNewStatus(status: BleManagerStatus) {
|
||||||
|
_status.value = status
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,17 +58,6 @@ internal class BPSManager @Inject constructor(
|
|||||||
|
|
||||||
private val intermediateCuffPressureCallback = object : IntermediateCuffPressureDataCallback() {
|
private val intermediateCuffPressureCallback = object : IntermediateCuffPressureDataCallback() {
|
||||||
|
|
||||||
override fun onDataReceived(device: BluetoothDevice, data: Data) {
|
|
||||||
log(
|
|
||||||
LogContract.Log.Level.APPLICATION,
|
|
||||||
"\"" + IntermediateCuffPressureParser.parse(data)
|
|
||||||
.toString() + "\" received"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Pass through received data
|
|
||||||
super.onDataReceived(device, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onIntermediateCuffPressureReceived(
|
override fun onIntermediateCuffPressureReceived(
|
||||||
device: BluetoothDevice,
|
device: BluetoothDevice,
|
||||||
cuffPressure: Float,
|
cuffPressure: Float,
|
||||||
@@ -95,17 +84,6 @@ internal class BPSManager @Inject constructor(
|
|||||||
|
|
||||||
private val bloodPressureMeasurementDataCallback = object : BloodPressureMeasurementDataCallback() {
|
private val bloodPressureMeasurementDataCallback = object : BloodPressureMeasurementDataCallback() {
|
||||||
|
|
||||||
override fun onDataReceived(device: BluetoothDevice, data: Data) {
|
|
||||||
log(
|
|
||||||
LogContract.Log.Level.APPLICATION,
|
|
||||||
"\"" + BloodPressureMeasurementParser.parse(data)
|
|
||||||
.toString() + "\" received"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Pass through received data
|
|
||||||
super.onDataReceived(device, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBloodPressureMeasurementReceived(
|
override fun onBloodPressureMeasurementReceived(
|
||||||
device: BluetoothDevice,
|
device: BluetoothDevice,
|
||||||
systolic: Float,
|
systolic: Float,
|
||||||
@@ -138,10 +116,6 @@ internal class BPSManager @Inject constructor(
|
|||||||
dataHolder.setBatteryLevel(batteryLevel)
|
dataHolder.setBatteryLevel(batteryLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* BluetoothGatt callbacks for connection/disconnection, service discovery,
|
|
||||||
* receiving notification, etc.
|
|
||||||
*/
|
|
||||||
private inner class BloodPressureManagerGattCallback : BatteryManagerGattCallback() {
|
private inner class BloodPressureManagerGattCallback : BatteryManagerGattCallback() {
|
||||||
|
|
||||||
override fun initialize() {
|
override fun initialize() {
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2015, Nordic Semiconductor
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
|
|
||||||
* documentation and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
|
|
||||||
* software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
|
||||||
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
package no.nordicsemi.android.bps.repository
|
|
||||||
|
|
||||||
import no.nordicsemi.android.ble.data.Data
|
|
||||||
import no.nordicsemi.android.bps.repository.DateTimeParser.parse
|
|
||||||
import java.lang.StringBuilder
|
|
||||||
|
|
||||||
object BloodPressureMeasurementParser {
|
|
||||||
|
|
||||||
fun parse(data: Data): String {
|
|
||||||
val builder = StringBuilder()
|
|
||||||
|
|
||||||
// first byte - flags
|
|
||||||
var offset = 0
|
|
||||||
val flags = data.getIntValue(Data.FORMAT_UINT8, offset++)!!
|
|
||||||
val unitType = flags and 0x01
|
|
||||||
val timestampPresent = flags and 0x02 > 0
|
|
||||||
val pulseRatePresent = flags and 0x04 > 0
|
|
||||||
val userIdPresent = flags and 0x08 > 0
|
|
||||||
val statusPresent = flags and 0x10 > 0
|
|
||||||
|
|
||||||
// following bytes - systolic, diastolic and mean arterial pressure
|
|
||||||
val systolic = data.getFloatValue(Data.FORMAT_SFLOAT, offset)!!
|
|
||||||
val diastolic = data.getFloatValue(Data.FORMAT_SFLOAT, offset + 2)!!
|
|
||||||
val meanArterialPressure = data.getFloatValue(Data.FORMAT_SFLOAT, offset + 4)!!
|
|
||||||
val unit = if (unitType == 0) " mmHg" else " kPa"
|
|
||||||
offset += 6
|
|
||||||
builder.append("Systolic: ").append(systolic).append(unit)
|
|
||||||
builder.append("\nDiastolic: ").append(diastolic).append(unit)
|
|
||||||
builder.append("\nMean AP: ").append(meanArterialPressure).append(unit)
|
|
||||||
|
|
||||||
// parse timestamp if present
|
|
||||||
if (timestampPresent) {
|
|
||||||
builder.append("\nTimestamp: ").append(parse(data, offset))
|
|
||||||
offset += 7
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse pulse rate if present
|
|
||||||
if (pulseRatePresent) {
|
|
||||||
val pulseRate = data.getFloatValue(Data.FORMAT_SFLOAT, offset)!!
|
|
||||||
offset += 2
|
|
||||||
builder.append("\nPulse: ").append(pulseRate).append(" bpm")
|
|
||||||
}
|
|
||||||
if (userIdPresent) {
|
|
||||||
val userId = data.getIntValue(Data.FORMAT_UINT8, offset)!!
|
|
||||||
offset += 1
|
|
||||||
builder.append("\nUser ID: ").append(userId)
|
|
||||||
}
|
|
||||||
if (statusPresent) {
|
|
||||||
val status = data.getIntValue(Data.FORMAT_UINT16, offset)!!
|
|
||||||
// offset += 2;
|
|
||||||
if (status and 0x0001 > 0) builder.append("\nBody movement detected")
|
|
||||||
if (status and 0x0002 > 0) builder.append("\nCuff too lose")
|
|
||||||
if (status and 0x0004 > 0) builder.append("\nIrregular pulse detected")
|
|
||||||
if (status and 0x0018 == 0x0008) builder.append("\nPulse rate exceeds upper limit")
|
|
||||||
if (status and 0x0018 == 0x0010) builder.append("\nPulse rate is less than lower limit")
|
|
||||||
if (status and 0x0018 == 0x0018) builder.append("\nPulse rate range: Reserved for future use ")
|
|
||||||
if (status and 0x0020 > 0) builder.append("\nImproper measurement position")
|
|
||||||
}
|
|
||||||
return builder.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2015, Nordic Semiconductor
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
|
|
||||||
* documentation and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
|
|
||||||
* software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
|
||||||
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
package no.nordicsemi.android.bps.repository
|
|
||||||
|
|
||||||
import no.nordicsemi.android.ble.common.callback.DateTimeDataCallback
|
|
||||||
import no.nordicsemi.android.ble.data.Data
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
object DateTimeParser {
|
|
||||||
/**
|
|
||||||
* Parses the date and time info.
|
|
||||||
*
|
|
||||||
* @param data
|
|
||||||
* @return time in human readable format
|
|
||||||
*/
|
|
||||||
fun parse(data: Data): String {
|
|
||||||
return parse(data, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the date and time info. This data has 7 bytes
|
|
||||||
*
|
|
||||||
* @param data
|
|
||||||
* @param offset
|
|
||||||
* offset to start reading the time
|
|
||||||
* @return time in human readable format
|
|
||||||
*/
|
|
||||||
/* package */
|
|
||||||
@JvmStatic
|
|
||||||
fun parse(data: Data, offset: Int): String {
|
|
||||||
val calendar = DateTimeDataCallback.readDateTime(data, offset)
|
|
||||||
return String.format(Locale.US, "%1\$te %1\$tb %1\$tY, %1\$tH:%1\$tM:%1\$tS", calendar)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2015, Nordic Semiconductor
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
|
|
||||||
* documentation and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
|
|
||||||
* software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
|
||||||
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
package no.nordicsemi.android.bps.repository
|
|
||||||
|
|
||||||
import no.nordicsemi.android.ble.data.Data
|
|
||||||
import no.nordicsemi.android.bps.repository.DateTimeParser.parse
|
|
||||||
import java.lang.StringBuilder
|
|
||||||
|
|
||||||
object IntermediateCuffPressureParser {
|
|
||||||
|
|
||||||
fun parse(data: Data): String {
|
|
||||||
val builder = StringBuilder()
|
|
||||||
|
|
||||||
// first byte - flags
|
|
||||||
var offset = 0
|
|
||||||
val flags = data.getIntValue(Data.FORMAT_UINT8, offset++)!!
|
|
||||||
val unitType = flags and 0x01
|
|
||||||
val timestampPresent = flags and 0x02 > 0
|
|
||||||
val pulseRatePresent = flags and 0x04 > 0
|
|
||||||
val userIdPresent = flags and 0x08 > 0
|
|
||||||
val statusPresent = flags and 0x10 > 0
|
|
||||||
|
|
||||||
// following bytes - pressure
|
|
||||||
val pressure = data.getFloatValue(Data.FORMAT_SFLOAT, offset)!!
|
|
||||||
val unit = if (unitType == 0) " mmHg" else " kPa"
|
|
||||||
offset += 6
|
|
||||||
builder.append("Cuff pressure: ").append(pressure).append(unit)
|
|
||||||
|
|
||||||
// parse timestamp if present
|
|
||||||
if (timestampPresent) {
|
|
||||||
builder.append("Timestamp: ").append(parse(data, offset))
|
|
||||||
offset += 7
|
|
||||||
}
|
|
||||||
|
|
||||||
// parse pulse rate if present
|
|
||||||
if (pulseRatePresent) {
|
|
||||||
val pulseRate = data.getFloatValue(Data.FORMAT_SFLOAT, offset)!!
|
|
||||||
offset += 2
|
|
||||||
builder.append("\nPulse: ").append(pulseRate).append(" bpm")
|
|
||||||
}
|
|
||||||
if (userIdPresent) {
|
|
||||||
val userId = data.getIntValue(Data.FORMAT_UINT8, offset)!!
|
|
||||||
offset += 1
|
|
||||||
builder.append("\nUser ID: ").append(userId)
|
|
||||||
}
|
|
||||||
if (statusPresent) {
|
|
||||||
val status = data.getIntValue(Data.FORMAT_UINT16, offset)!!
|
|
||||||
// offset += 2;
|
|
||||||
if (status and 0x0001 > 0) builder.append("\nBody movement detected")
|
|
||||||
if (status and 0x0002 > 0) builder.append("\nCuff too lose")
|
|
||||||
if (status and 0x0004 > 0) builder.append("\nIrregular pulse detected")
|
|
||||||
if (status and 0x0018 == 0x0008) builder.append("\nPulse rate exceeds upper limit")
|
|
||||||
if (status and 0x0018 == 0x0010) builder.append("\nPulse rate is less than lower limit")
|
|
||||||
if (status and 0x0018 == 0x0018) builder.append("\nPulse rate range: Reserved for future use ")
|
|
||||||
if (status and 0x0020 > 0) builder.append("\nImproper measurement position")
|
|
||||||
}
|
|
||||||
return builder.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,36 +7,37 @@ import androidx.compose.runtime.collectAsState
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import no.nordicsemi.android.bps.R
|
import no.nordicsemi.android.bps.R
|
||||||
import no.nordicsemi.android.bps.data.BPSData
|
|
||||||
import no.nordicsemi.android.bps.viewmodel.BPSViewModel
|
import no.nordicsemi.android.bps.viewmodel.BPSViewModel
|
||||||
import no.nordicsemi.android.theme.view.BackIconAppBar
|
import no.nordicsemi.android.theme.view.BackIconAppBar
|
||||||
|
import no.nordicsemi.android.theme.view.DeviceConnectingView
|
||||||
|
import no.nordicsemi.android.utils.exhaustive
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BPSScreen(finishAction: () -> Unit) {
|
fun BPSScreen(finishAction: () -> Unit) {
|
||||||
val viewModel: BPSViewModel = hiltViewModel()
|
val viewModel: BPSViewModel = hiltViewModel()
|
||||||
val state = viewModel.state.collectAsState().value
|
val state = viewModel.state.collectAsState().value
|
||||||
val isScreenActive = viewModel.isActive.collectAsState().value
|
|
||||||
|
|
||||||
LaunchedEffect("connect") {
|
LaunchedEffect(state.isActive) {
|
||||||
viewModel.connectDevice()
|
if (state.isActive) {
|
||||||
}
|
viewModel.connectDevice()
|
||||||
|
} else {
|
||||||
LaunchedEffect(isScreenActive) {
|
|
||||||
if (!isScreenActive) {
|
|
||||||
finishAction()
|
finishAction()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BPSView(state) { viewModel.onEvent(it) }
|
BPSView(state.viewState) { viewModel.onEvent(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun BPSView(state: BPSData, onEvent: (BPSScreenViewEvent) -> Unit) {
|
private fun BPSView(state: BPSViewState, onEvent: (BPSScreenViewEvent) -> Unit) {
|
||||||
Column {
|
Column {
|
||||||
BackIconAppBar(stringResource(id = R.string.bps_title)) {
|
BackIconAppBar(stringResource(id = R.string.bps_title)) {
|
||||||
onEvent(DisconnectEvent)
|
onEvent(DisconnectEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
BPSContentView(state) { onEvent(it) }
|
when (state) {
|
||||||
|
is DisplayDataState -> BPSContentView(state.data) { onEvent(it) }
|
||||||
|
LoadingState -> DeviceConnectingView()
|
||||||
|
}.exhaustive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package no.nordicsemi.android.bps.view
|
||||||
|
|
||||||
|
import no.nordicsemi.android.bps.data.BPSData
|
||||||
|
|
||||||
|
internal data class BPSState(
|
||||||
|
val viewState: BPSViewState,
|
||||||
|
val isActive: Boolean = true
|
||||||
|
)
|
||||||
|
|
||||||
|
internal sealed class BPSViewState
|
||||||
|
|
||||||
|
internal object LoadingState : BPSViewState()
|
||||||
|
|
||||||
|
internal data class DisplayDataState(val data: BPSData) : BPSViewState()
|
||||||
@@ -1,12 +1,22 @@
|
|||||||
package no.nordicsemi.android.bps.viewmodel
|
package no.nordicsemi.android.bps.viewmodel
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothDevice
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import no.nordicsemi.android.bps.data.BPSRepository
|
import no.nordicsemi.android.bps.data.BPSRepository
|
||||||
import no.nordicsemi.android.bps.repository.BPSManager
|
import no.nordicsemi.android.bps.repository.BPSManager
|
||||||
import no.nordicsemi.android.bps.view.BPSScreenViewEvent
|
import no.nordicsemi.android.bps.view.BPSScreenViewEvent
|
||||||
|
import no.nordicsemi.android.bps.view.BPSState
|
||||||
import no.nordicsemi.android.bps.view.DisconnectEvent
|
import no.nordicsemi.android.bps.view.DisconnectEvent
|
||||||
|
import no.nordicsemi.android.bps.view.DisplayDataState
|
||||||
|
import no.nordicsemi.android.bps.view.LoadingState
|
||||||
|
import no.nordicsemi.android.service.BleManagerStatus
|
||||||
|
import no.nordicsemi.android.service.ConnectionObserverAdapter
|
||||||
import no.nordicsemi.android.service.SelectedBluetoothDeviceHolder
|
import no.nordicsemi.android.service.SelectedBluetoothDeviceHolder
|
||||||
import no.nordicsemi.android.theme.viewmodel.CloseableViewModel
|
|
||||||
import no.nordicsemi.android.utils.exhaustive
|
import no.nordicsemi.android.utils.exhaustive
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -14,10 +24,30 @@ import javax.inject.Inject
|
|||||||
internal class BPSViewModel @Inject constructor(
|
internal class BPSViewModel @Inject constructor(
|
||||||
private val bpsManager: BPSManager,
|
private val bpsManager: BPSManager,
|
||||||
private val deviceHolder: SelectedBluetoothDeviceHolder,
|
private val deviceHolder: SelectedBluetoothDeviceHolder,
|
||||||
private val dataHolder: BPSRepository
|
private val repository: BPSRepository
|
||||||
) : CloseableViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
val state = dataHolder.data
|
val state = repository.data.combine(repository.status) { data, status ->
|
||||||
|
when (status) {
|
||||||
|
BleManagerStatus.CONNECTING -> BPSState(LoadingState)
|
||||||
|
BleManagerStatus.OK -> BPSState(DisplayDataState(data))
|
||||||
|
BleManagerStatus.DISCONNECTED -> BPSState(DisplayDataState(data), false)
|
||||||
|
}
|
||||||
|
}.stateIn(viewModelScope, SharingStarted.Lazily, BPSState(LoadingState))
|
||||||
|
|
||||||
|
init {
|
||||||
|
bpsManager.setConnectionObserver(object : ConnectionObserverAdapter() {
|
||||||
|
override fun onDeviceConnected(device: BluetoothDevice) {
|
||||||
|
super.onDeviceConnected(device)
|
||||||
|
repository.setNewStatus(BleManagerStatus.OK)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDeviceDisconnected(device: BluetoothDevice, reason: Int) {
|
||||||
|
super.onDeviceDisconnected(device, reason)
|
||||||
|
repository.setNewStatus(BleManagerStatus.DISCONNECTED)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fun onEvent(event: BPSScreenViewEvent) {
|
fun onEvent(event: BPSScreenViewEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
@@ -26,17 +56,14 @@ internal class BPSViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun connectDevice() {
|
fun connectDevice() {
|
||||||
deviceHolder.device?.let {
|
bpsManager.connect(deviceHolder.device!!.device)
|
||||||
bpsManager.connect(it.device)
|
.useAutoConnect(false)
|
||||||
.useAutoConnect(false)
|
.retry(3, 100)
|
||||||
.retry(3, 100)
|
.enqueue()
|
||||||
.enqueue()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onDisconnectButtonClick() {
|
private fun onDisconnectButtonClick() {
|
||||||
finish()
|
|
||||||
deviceHolder.forgetDevice()
|
deviceHolder.forgetDevice()
|
||||||
dataHolder.clear()
|
bpsManager.disconnect().enqueue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package no.nordicsemi.android.csc.data
|
package no.nordicsemi.android.csc.data
|
||||||
|
|
||||||
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import no.nordicsemi.android.csc.view.SpeedUnit
|
import no.nordicsemi.android.csc.view.SpeedUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@@ -12,6 +15,9 @@ internal class CSCRepository @Inject constructor() {
|
|||||||
private val _data = MutableStateFlow(CSCData())
|
private val _data = MutableStateFlow(CSCData())
|
||||||
val data: StateFlow<CSCData> = _data
|
val data: StateFlow<CSCData> = _data
|
||||||
|
|
||||||
|
private val _command = MutableSharedFlow<CSCServiceCommand>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_LATEST)
|
||||||
|
val command = _command.asSharedFlow()
|
||||||
|
|
||||||
fun setWheelSize(wheelSize: Int, wheelSizeDisplay: String) {
|
fun setWheelSize(wheelSize: Int, wheelSizeDisplay: String) {
|
||||||
_data.tryEmit(_data.value.copy(
|
_data.tryEmit(_data.value.copy(
|
||||||
wheelSize = wheelSize,
|
wheelSize = wheelSize,
|
||||||
@@ -44,6 +50,10 @@ internal class CSCRepository @Inject constructor() {
|
|||||||
_data.tryEmit(_data.value.copy(batteryLevel = batteryLevel))
|
_data.tryEmit(_data.value.copy(batteryLevel = batteryLevel))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun sendNewServiceCommand(workingMode: CSCServiceCommand) {
|
||||||
|
_command.tryEmit(workingMode)
|
||||||
|
}
|
||||||
|
|
||||||
fun clear() {
|
fun clear() {
|
||||||
_data.tryEmit(CSCData())
|
_data.tryEmit(CSCData())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package no.nordicsemi.android.csc.data
|
||||||
|
|
||||||
|
internal sealed class CSCServiceCommand
|
||||||
|
|
||||||
|
internal data class SetWheelSizeCommand(val size: Int) : CSCServiceCommand()
|
||||||
|
|
||||||
|
internal object DisconnectCommand : CSCServiceCommand()
|
||||||
@@ -70,12 +70,6 @@ internal class CSCManager(context: Context, private val dataHolder: CSCRepositor
|
|||||||
// CSC characteristic is required
|
// CSC characteristic is required
|
||||||
setNotificationCallback(cscMeasurementCharacteristic)
|
setNotificationCallback(cscMeasurementCharacteristic)
|
||||||
.with(object : CyclingSpeedAndCadenceMeasurementDataCallback() {
|
.with(object : CyclingSpeedAndCadenceMeasurementDataCallback() {
|
||||||
override fun onDataReceived(device: BluetoothDevice, data: Data) {
|
|
||||||
log(LogContract.Log.Level.APPLICATION, "\"" + parse(data) + "\" received")
|
|
||||||
|
|
||||||
// Pass through received data
|
|
||||||
super.onDataReceived(device, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getWheelCircumference(): Float {
|
override fun getWheelCircumference(): Float {
|
||||||
return wheelSize.toFloat()
|
return wheelSize.toFloat()
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2015, Nordic Semiconductor
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
|
|
||||||
* documentation and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this
|
|
||||||
* software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
|
||||||
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
package no.nordicsemi.android.csc.repository
|
|
||||||
|
|
||||||
import no.nordicsemi.android.ble.data.Data
|
|
||||||
|
|
||||||
object CSCMeasurementParser {
|
|
||||||
|
|
||||||
private const val WHEEL_REV_DATA_PRESENT: Byte = 0x01 // 1 bit
|
|
||||||
private const val CRANK_REV_DATA_PRESENT: Byte = 0x02 // 1 bit
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun parse(data: Data): String {
|
|
||||||
var offset = 0
|
|
||||||
val flags = data.getByte(offset)!!.toInt() // 1 byte
|
|
||||||
offset += 1
|
|
||||||
val wheelRevPresent = flags and WHEEL_REV_DATA_PRESENT.toInt() > 0
|
|
||||||
val crankRevPreset = flags and CRANK_REV_DATA_PRESENT.toInt() > 0
|
|
||||||
var wheelRevolutions = 0
|
|
||||||
var lastWheelEventTime = 0
|
|
||||||
if (wheelRevPresent) {
|
|
||||||
wheelRevolutions = data.getIntValue(Data.FORMAT_UINT32, offset)!!
|
|
||||||
offset += 4
|
|
||||||
lastWheelEventTime = data.getIntValue(Data.FORMAT_UINT16, offset)!! // 1/1024 s
|
|
||||||
offset += 2
|
|
||||||
}
|
|
||||||
var crankRevolutions = 0
|
|
||||||
var lastCrankEventTime = 0
|
|
||||||
if (crankRevPreset) {
|
|
||||||
crankRevolutions = data.getIntValue(Data.FORMAT_UINT16, offset)!!
|
|
||||||
offset += 2
|
|
||||||
lastCrankEventTime = data.getIntValue(Data.FORMAT_UINT16, offset)!!
|
|
||||||
//offset += 2;
|
|
||||||
}
|
|
||||||
val builder = StringBuilder()
|
|
||||||
if (wheelRevPresent) {
|
|
||||||
builder.append("Wheel rev: ").append(wheelRevolutions).append(",\n")
|
|
||||||
builder.append("Last wheel event time: ").append(lastWheelEventTime).append(",\n")
|
|
||||||
}
|
|
||||||
if (crankRevPreset) {
|
|
||||||
builder.append("Crank rev: ").append(crankRevolutions).append(",\n")
|
|
||||||
builder.append("Last crank event time: ").append(lastCrankEventTime).append(",\n")
|
|
||||||
}
|
|
||||||
if (!wheelRevPresent && !crankRevPreset) {
|
|
||||||
builder.append("No wheel or crank data")
|
|
||||||
}
|
|
||||||
builder.setLength(builder.length - 2)
|
|
||||||
return builder.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,31 @@
|
|||||||
package no.nordicsemi.android.csc.repository
|
package no.nordicsemi.android.csc.repository
|
||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
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.CSCRepository
|
||||||
|
import no.nordicsemi.android.csc.data.DisconnectCommand
|
||||||
|
import no.nordicsemi.android.csc.data.SetWheelSizeCommand
|
||||||
import no.nordicsemi.android.service.ForegroundBleService
|
import no.nordicsemi.android.service.ForegroundBleService
|
||||||
|
import no.nordicsemi.android.utils.exhaustive
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
internal class CSCService : ForegroundBleService() {
|
internal class CSCService : ForegroundBleService() {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var dataHolder: CSCRepository
|
lateinit var repository: CSCRepository
|
||||||
|
|
||||||
override val manager: CSCManager by lazy { CSCManager(this, dataHolder) }
|
override val manager: CSCManager by lazy { CSCManager(this, repository) }
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
|
||||||
|
repository.command.onEach {
|
||||||
|
when (it) {
|
||||||
|
DisconnectCommand -> stopSelf()
|
||||||
|
is SetWheelSizeCommand -> manager.setWheelSize(it.size)
|
||||||
|
}.exhaustive
|
||||||
|
}.launchIn(scope)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package no.nordicsemi.android.csc.viewmodel
|
package no.nordicsemi.android.csc.viewmodel
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import no.nordicsemi.android.csc.data.CSCRepository
|
import no.nordicsemi.android.csc.data.CSCRepository
|
||||||
import no.nordicsemi.android.csc.view.CSCViewEvent
|
import no.nordicsemi.android.csc.view.CSCViewEvent
|
||||||
@@ -8,14 +9,13 @@ import no.nordicsemi.android.csc.view.OnDisconnectButtonClick
|
|||||||
import no.nordicsemi.android.csc.view.OnSelectedSpeedUnitSelected
|
import no.nordicsemi.android.csc.view.OnSelectedSpeedUnitSelected
|
||||||
import no.nordicsemi.android.csc.view.OnShowEditWheelSizeDialogButtonClick
|
import no.nordicsemi.android.csc.view.OnShowEditWheelSizeDialogButtonClick
|
||||||
import no.nordicsemi.android.csc.view.OnWheelSizeSelected
|
import no.nordicsemi.android.csc.view.OnWheelSizeSelected
|
||||||
import no.nordicsemi.android.theme.viewmodel.CloseableViewModel
|
|
||||||
import no.nordicsemi.android.utils.exhaustive
|
import no.nordicsemi.android.utils.exhaustive
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
internal class CSCViewModel @Inject constructor(
|
internal class CSCViewModel @Inject constructor(
|
||||||
private val dataHolder: CSCRepository
|
private val dataHolder: CSCRepository
|
||||||
) : CloseableViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
val state = dataHolder.data
|
val state = dataHolder.data
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import kotlinx.coroutines.flow.SharingStarted
|
|||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import no.nordicsemi.android.service.BleManagerStatus
|
import no.nordicsemi.android.service.BleManagerStatus
|
||||||
import no.nordicsemi.android.theme.viewmodel.CloseableViewModel
|
|
||||||
import no.nordicsemi.android.uart.data.DisconnectCommand
|
import no.nordicsemi.android.uart.data.DisconnectCommand
|
||||||
import no.nordicsemi.android.uart.data.SendTextCommand
|
import no.nordicsemi.android.uart.data.SendTextCommand
|
||||||
import no.nordicsemi.android.uart.data.UARTRepository
|
import no.nordicsemi.android.uart.data.UARTRepository
|
||||||
|
|||||||
Reference in New Issue
Block a user