Remove CloseableViewModel

This commit is contained in:
Sylwester Zieliński
2022-01-17 17:01:52 +01:00
parent 4aa0a69256
commit 2a9b66c357
16 changed files with 111 additions and 355 deletions

View File

@@ -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)
}
}

View File

@@ -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
}
} }

View File

@@ -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() {

View File

@@ -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()
}
}

View File

@@ -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)
}
}

View File

@@ -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()
}
}

View File

@@ -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
} }
} }

View File

@@ -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()

View File

@@ -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()
} }
} }

View File

@@ -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())
} }

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()
}
}

View File

@@ -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)
}
} }

View File

@@ -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

View File

@@ -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