Add accelerometer history
This commit is contained in:
parent
c47c371530
commit
4528a7cd9a
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="1.8.10" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -13,8 +13,8 @@ android {
|
|||
applicationId "llc.arma.ble"
|
||||
minSdk 24
|
||||
targetSdk 33
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
versionCode 2
|
||||
versionName "1.1"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
|
|
|
|||
|
|
@ -56,6 +56,10 @@ class MainActivity : ComponentActivity() {
|
|||
mutableStateOf<@Composable () -> Unit>({})
|
||||
}
|
||||
|
||||
if(modalState.currentValue == ModalBottomSheetValue.Hidden){
|
||||
sheetContent = {}
|
||||
}
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalBottomDialogState provides BottomState(
|
||||
sheetState = modalState,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
package llc.arma.ble.app.ui.common
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.ContentAlpha
|
||||
import androidx.compose.material.LocalContentColor
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun SignalLevel(
|
||||
modifier: Modifier = Modifier,
|
||||
maxLevel: Int = 5,
|
||||
level: Int
|
||||
){
|
||||
|
||||
val step = (16 - 4) / 4
|
||||
|
||||
Row(
|
||||
modifier = modifier.height(16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(2.dp),
|
||||
verticalAlignment = Alignment.Bottom
|
||||
) {
|
||||
|
||||
for(col in 0..4 step 1){
|
||||
Surface(
|
||||
color = LocalContentColor.current.copy(
|
||||
alpha = if(col <= level + 1) ContentAlpha.high else ContentAlpha.disabled
|
||||
),
|
||||
shape = CircleShape,
|
||||
modifier = Modifier
|
||||
.width(4.dp)
|
||||
.defaultMinSize(minHeight = 4.dp)
|
||||
.height(((col + 1) * step).dp)
|
||||
) { }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -35,6 +35,9 @@ class BleMapper @Inject constructor(
|
|||
is Ble.Accelerometer -> {
|
||||
BleView.Accelerometer(
|
||||
info = input.info,
|
||||
state = BleView.BleState(
|
||||
tx = txMapper.map(input.state.tx)
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,10 @@ class BleViewMapper @Inject constructor(
|
|||
|
||||
is BleView.Accelerometer -> {
|
||||
Ble.Accelerometer(
|
||||
info = input.info
|
||||
info = input.info,
|
||||
state = Ble.BleState(
|
||||
tx = txMapper.map(input.state.tx)
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ sealed class BleView(
|
|||
) {
|
||||
|
||||
class Accelerometer(
|
||||
info: BleInfo
|
||||
info: BleInfo,
|
||||
val state: BleState
|
||||
) : BleView(info)
|
||||
|
||||
class Beacon(
|
||||
|
|
@ -58,9 +59,27 @@ sealed class BleView(
|
|||
MINUS_4(-4),
|
||||
ZERO(0),
|
||||
PLUS_3(3),
|
||||
PLUS_4(4)
|
||||
PLUS_4(4);
|
||||
|
||||
val powerPercentage: Int
|
||||
get() {
|
||||
return when(this){
|
||||
MINUS_40 -> 1
|
||||
MINUS_20 -> 5
|
||||
MINUS_16 -> 7
|
||||
MINUS_12 -> 10
|
||||
MINUS_8 -> 16
|
||||
MINUS_4 -> 20
|
||||
ZERO -> 40
|
||||
PLUS_3 -> 80
|
||||
PLUS_4 -> 100
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ import androidx.compose.material3.*
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableLongStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
|
|
@ -29,9 +30,11 @@ import kotlinx.coroutines.delay
|
|||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import llc.arma.ble.app.ui.common.SignalLevel
|
||||
import llc.arma.ble.app.ui.common.rememberBottomDialogState
|
||||
import llc.arma.ble.domain.model.BleInfo
|
||||
import llc.arma.ble.domain.model.ConnectedBleInfo
|
||||
import kotlin.math.pow
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
|
|
@ -184,6 +187,16 @@ private fun ItemIcon(
|
|||
|
||||
}
|
||||
|
||||
private fun Int.toSignalLevel(): Int {
|
||||
return when(this){
|
||||
in -30 downTo -52 -> 4
|
||||
in -51 downTo -63 -> 3
|
||||
in -62 downTo -75 -> 2
|
||||
in -74 downTo -89 -> 1
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BleItem(
|
||||
ble: BleInfo,
|
||||
|
|
@ -254,6 +267,41 @@ private fun BleItem(
|
|||
text = ble.serial
|
||||
)
|
||||
|
||||
/*Text(
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
text = String.format("%.3f", (10.0.pow((ble.tx.toDouble() - (ble.rssi?.toDouble() ?: 0.0) - 74) / 20)))
|
||||
)
|
||||
|
||||
Text(
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
text = String.format("%.3f", (ble.tx.toDouble() - (ble.rssi?.toDouble() ?: 0.0)))
|
||||
)
|
||||
|
||||
Text(
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
text = ble.tx.toString() + " tx"
|
||||
)*/
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.alpha(0.7f)
|
||||
) {
|
||||
|
||||
Icon(
|
||||
modifier = Modifier.size(16.dp),
|
||||
imageVector = Icons.Rounded.CompareArrows,
|
||||
contentDescription = null
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
|
||||
Text(
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
text = String.format("%.3f", (10.0.pow((ble.tx.toDouble() - (ble.rssi?.toDouble() ?: 0.0) - 74) / 20))) + " м."
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
|
|
@ -263,11 +311,9 @@ private fun BleItem(
|
|||
modifier = Modifier.alpha(0.7f)
|
||||
) {
|
||||
|
||||
Icon(
|
||||
modifier = Modifier.size(16.dp),
|
||||
imageVector = Icons.Rounded.NetworkCell,
|
||||
contentDescription = null
|
||||
)
|
||||
SignalLevel(level = ble.rssi?.toSignalLevel() ?: 0)
|
||||
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
|
||||
Box {
|
||||
|
||||
|
|
|
|||
|
|
@ -15,10 +15,26 @@ class AccelerometerContract {
|
|||
|
||||
object OnHideAccelerometerMeasure : Event()
|
||||
|
||||
object OnShowAccelerometerHistory : Event()
|
||||
|
||||
object OnHideAccelerometerHistory : Event()
|
||||
|
||||
object OnPowerEdit : Event()
|
||||
|
||||
object OnShowWriteBlePreview : Event()
|
||||
|
||||
object OnWriteBle : Event()
|
||||
|
||||
object OnHideWriteBlePreview : Event()
|
||||
|
||||
data class OnBleChanged(
|
||||
val ble: Ble.Accelerometer,
|
||||
): Event()
|
||||
|
||||
data class OnPowerChanged(
|
||||
val tx: BleView.BleState.TX
|
||||
) : Event()
|
||||
|
||||
}
|
||||
|
||||
sealed class State : ViewState {
|
||||
|
|
@ -28,7 +44,26 @@ class AccelerometerContract {
|
|||
data class Display(
|
||||
val origin: Ble.Accelerometer,
|
||||
val accelerometer: BleView.Accelerometer,
|
||||
) : State()
|
||||
val writeState: WriteState?
|
||||
) : State() {
|
||||
|
||||
sealed class WriteState {
|
||||
|
||||
data class DisplayPreview(
|
||||
val writeRequest: Ble.Accelerometer.WriteRequest
|
||||
) : WriteState()
|
||||
|
||||
data class Writing(
|
||||
val writeRequest: Ble.Accelerometer.WriteRequest
|
||||
) : WriteState()
|
||||
|
||||
object Success : WriteState()
|
||||
|
||||
object Failure : WriteState()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -36,6 +71,16 @@ class AccelerometerContract {
|
|||
|
||||
object ShowAccelerometerMeasure : Effect()
|
||||
|
||||
object ShowAccelerometerHistory : Effect()
|
||||
|
||||
object ShowPowerPicker : Effect()
|
||||
|
||||
object HidePowerPicker : Effect()
|
||||
|
||||
object ShowWriteBle : Effect()
|
||||
|
||||
object HideWriteBle : Effect()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
package llc.arma.ble.app.ui.screen.inspection.accelerometer
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.ModalBottomSheetValue
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
|
|
@ -15,16 +15,18 @@ import kotlinx.coroutines.flow.launchIn
|
|||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import llc.arma.ble.app.ui.common.rememberBottomDialogState
|
||||
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelerometerHistory
|
||||
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelerometerMeasure
|
||||
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.DisplayState
|
||||
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.LoadingState
|
||||
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.PowerEdit
|
||||
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.Write
|
||||
import llc.arma.ble.domain.model.Ble
|
||||
|
||||
enum class SheetPage {
|
||||
MEASURE_HISTORY
|
||||
MEASURE, POWER, WRITE, HISTORY
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun AccelerometerScreen(
|
||||
ble: Ble.Accelerometer,
|
||||
|
|
@ -43,9 +45,11 @@ fun AccelerometerScreen(
|
|||
mutableStateOf<SheetPage?>(null)
|
||||
}
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
LaunchedEffect(sheetPage) {
|
||||
when (sheetPage) {
|
||||
SheetPage.MEASURE_HISTORY -> launch {
|
||||
SheetPage.MEASURE -> launch {
|
||||
val currentState = viewModel.viewState.value
|
||||
|
||||
if (currentState is AccelerometerContract.State.Display) {
|
||||
|
|
@ -54,19 +58,93 @@ fun AccelerometerScreen(
|
|||
}
|
||||
}
|
||||
}
|
||||
SheetPage.HISTORY -> launch {
|
||||
val currentState = viewModel.viewState.value
|
||||
|
||||
if (currentState is AccelerometerContract.State.Display) {
|
||||
bottomDialog.show {
|
||||
AccelerometerHistory(ble = currentState.accelerometer.info)
|
||||
}
|
||||
}
|
||||
}
|
||||
SheetPage.POWER -> bottomDialog.show {
|
||||
|
||||
val currentState = viewModel.viewState.value
|
||||
|
||||
if(currentState is AccelerometerContract.State.Display) {
|
||||
PowerEdit(
|
||||
state = currentState.accelerometer,
|
||||
onEvent = {
|
||||
viewModel.setEvent(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
SheetPage.WRITE -> bottomDialog.show {
|
||||
|
||||
val currentState = viewModel.viewState.value
|
||||
|
||||
if (currentState is AccelerometerContract.State.Display) {
|
||||
|
||||
currentState.writeState?.let {
|
||||
|
||||
Write(
|
||||
state = it,
|
||||
onEvent = {
|
||||
viewModel.setEvent(it)
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
null -> {
|
||||
bottomDialog.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DisposableEffect(key1 = Unit, effect = {
|
||||
onDispose {
|
||||
scope.launch {
|
||||
bottomDialog.hide()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
LaunchedEffect("effect"){
|
||||
viewModel.effect.onEach {
|
||||
when(it){
|
||||
AccelerometerContract.Effect.ShowAccelerometerMeasure -> {
|
||||
sheetPage = null
|
||||
delay(100)
|
||||
sheetPage = SheetPage.MEASURE_HISTORY
|
||||
sheetPage = SheetPage.MEASURE
|
||||
}
|
||||
is AccelerometerContract.Effect.HidePowerPicker -> launch {
|
||||
sheetPage = null
|
||||
delay(100)
|
||||
}
|
||||
is AccelerometerContract.Effect.ShowPowerPicker -> launch {
|
||||
sheetPage = null
|
||||
delay(100)
|
||||
sheetPage = SheetPage.POWER
|
||||
}
|
||||
is AccelerometerContract.Effect.HideWriteBle -> {
|
||||
sheetPage = null
|
||||
delay(100)
|
||||
}
|
||||
is AccelerometerContract.Effect.ShowWriteBle -> {
|
||||
sheetPage = null
|
||||
delay(100)
|
||||
sheetPage = SheetPage.WRITE
|
||||
}
|
||||
|
||||
AccelerometerContract.Effect.ShowAccelerometerHistory -> {
|
||||
sheetPage = null
|
||||
delay(100)
|
||||
sheetPage = SheetPage.HISTORY
|
||||
}
|
||||
}
|
||||
}.launchIn(this)
|
||||
|
|
|
|||
|
|
@ -1,16 +1,22 @@
|
|||
package llc.arma.ble.app.ui.screen.inspection.accelerometer
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import llc.arma.ble.app.ui.common.BaseViewModel
|
||||
import llc.arma.ble.app.ui.mapper.BleMapper
|
||||
import llc.arma.ble.app.ui.mapper.BleViewMapper
|
||||
import llc.arma.ble.app.ui.model.BleView
|
||||
import llc.arma.ble.app.ui.screen.inspection.thermometer.ThermometerContract
|
||||
import llc.arma.ble.domain.model.Ble
|
||||
import llc.arma.ble.domain.usecase.WriteBle
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class AccelerometerViewModel @Inject constructor(
|
||||
private val bleMapper: BleMapper
|
||||
private val bleMapper: BleMapper,
|
||||
private val bleViewMapper: BleViewMapper,
|
||||
private val writeBle: WriteBle
|
||||
) : BaseViewModel<AccelerometerContract.State, AccelerometerContract.Event, AccelerometerContract.Effect>() {
|
||||
|
||||
override fun setInitialState() = AccelerometerContract.State.Loading
|
||||
|
|
@ -20,6 +26,109 @@ class AccelerometerViewModel @Inject constructor(
|
|||
is AccelerometerContract.Event.OnBleChanged -> reduce(viewState.value, event)
|
||||
is AccelerometerContract.Event.OnHideAccelerometerMeasure -> reduce(viewState.value, event)
|
||||
is AccelerometerContract.Event.OnShowAccelerometerMeasure -> reduce(viewState.value, event)
|
||||
is AccelerometerContract.Event.OnPowerChanged -> reduce(viewState.value, event)
|
||||
is AccelerometerContract.Event.OnPowerEdit -> reduce(viewState.value, event)
|
||||
is AccelerometerContract.Event.OnShowWriteBlePreview -> reduce(viewState.value, event)
|
||||
is AccelerometerContract.Event.OnHideWriteBlePreview -> reduce(viewState.value, event)
|
||||
is AccelerometerContract.Event.OnWriteBle -> reduce(viewState.value, event)
|
||||
is AccelerometerContract.Event.OnHideAccelerometerHistory -> reduce(viewState.value, event)
|
||||
is AccelerometerContract.Event.OnShowAccelerometerHistory -> reduce(viewState.value, event)
|
||||
}
|
||||
}
|
||||
|
||||
private fun reduce(
|
||||
state: AccelerometerContract.State,
|
||||
event: AccelerometerContract.Event.OnShowAccelerometerHistory
|
||||
) {
|
||||
|
||||
setEffect {
|
||||
AccelerometerContract.Effect.ShowAccelerometerHistory
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun reduce(
|
||||
state: AccelerometerContract.State,
|
||||
event: AccelerometerContract.Event.OnHideAccelerometerHistory
|
||||
) {
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
private fun reduce(
|
||||
state: AccelerometerContract.State,
|
||||
event: AccelerometerContract.Event.OnHideWriteBlePreview
|
||||
) {
|
||||
|
||||
if(state is AccelerometerContract.State.Display){
|
||||
|
||||
setState {
|
||||
state.copy(
|
||||
writeState = null
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
setEffect {
|
||||
AccelerometerContract.Effect.HideWriteBle
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun reduce(
|
||||
state: AccelerometerContract.State,
|
||||
event: AccelerometerContract.Event.OnShowWriteBlePreview
|
||||
) {
|
||||
|
||||
if(state is AccelerometerContract.State.Display){
|
||||
|
||||
val newBle = bleViewMapper.map(state.accelerometer) as Ble.Accelerometer
|
||||
|
||||
val writeRequest = Ble.Accelerometer.WriteRequest(
|
||||
tx = if(newBle.state.tx == state.origin.state.tx) null else newBle.state.tx
|
||||
)
|
||||
|
||||
setState {
|
||||
state.copy(
|
||||
writeState = AccelerometerContract.State.Display.WriteState.DisplayPreview(
|
||||
writeRequest
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
setEffect {
|
||||
AccelerometerContract.Effect.ShowWriteBle
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun reduce(
|
||||
state: AccelerometerContract.State,
|
||||
event: AccelerometerContract.Event.OnPowerChanged
|
||||
) {
|
||||
|
||||
if(state is AccelerometerContract.State.Display) {
|
||||
|
||||
state.accelerometer.state.tx = event.tx
|
||||
|
||||
}
|
||||
|
||||
setEffect {
|
||||
AccelerometerContract.Effect.HidePowerPicker
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun reduce(
|
||||
state: AccelerometerContract.State,
|
||||
event: AccelerometerContract.Event.OnPowerEdit
|
||||
) {
|
||||
setEffect {
|
||||
AccelerometerContract.Effect.ShowPowerPicker
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -52,7 +161,8 @@ class AccelerometerViewModel @Inject constructor(
|
|||
is AccelerometerContract.State.Display -> setState {
|
||||
state.copy(
|
||||
origin = Ble.Accelerometer(
|
||||
info = event.ble.info
|
||||
info = event.ble.info,
|
||||
state = event.ble.state
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -60,11 +170,76 @@ class AccelerometerViewModel @Inject constructor(
|
|||
is AccelerometerContract.State.Loading -> setState {
|
||||
AccelerometerContract.State.Display(
|
||||
origin = event.ble,
|
||||
accelerometer = bleMapper.map(event.ble) as BleView.Accelerometer
|
||||
accelerometer = bleMapper.map(event.ble) as BleView.Accelerometer,
|
||||
writeState = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun reduce(
|
||||
state: AccelerometerContract.State,
|
||||
event: AccelerometerContract.Event.OnWriteBle
|
||||
) {
|
||||
|
||||
if(state is AccelerometerContract.State.Display){
|
||||
|
||||
state.writeState?.let { request ->
|
||||
|
||||
if(request is AccelerometerContract.State.Display.WriteState.DisplayPreview) {
|
||||
|
||||
viewModelScope.launch {
|
||||
|
||||
setState {
|
||||
state.copy(
|
||||
writeState = AccelerometerContract.State.Display.WriteState.Writing(
|
||||
request.writeRequest
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
writeBle(state.accelerometer.info.serial, request.writeRequest).fold(
|
||||
onSuccess = {
|
||||
|
||||
val currentState = viewState.value
|
||||
|
||||
if(currentState is AccelerometerContract.State.Display) {
|
||||
|
||||
val newBleObject = Ble.Accelerometer(
|
||||
info = currentState.origin.info,
|
||||
state = currentState.origin.state.copy(
|
||||
tx = request.writeRequest.tx ?: state.origin.state.tx
|
||||
)
|
||||
)
|
||||
|
||||
setState {
|
||||
currentState.copy(
|
||||
origin = newBleObject,
|
||||
writeState = AccelerometerContract.State.Display.WriteState.Success
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
onFailure = {
|
||||
setState {
|
||||
state.copy(
|
||||
writeState = AccelerometerContract.State.Display.WriteState.Failure
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,358 @@
|
|||
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
|
||||
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.gestures.scrollBy
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.patrykandpatrick.vico.compose.axis.horizontal.bottomAxis
|
||||
import com.patrykandpatrick.vico.compose.axis.vertical.startAxis
|
||||
import com.patrykandpatrick.vico.compose.chart.Chart
|
||||
import com.patrykandpatrick.vico.compose.chart.line.lineChart
|
||||
import com.patrykandpatrick.vico.core.entry.ChartEntryModelProducer
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import llc.arma.ble.app.ui.common.BaseViewModel
|
||||
import llc.arma.ble.app.ui.common.ViewEvent
|
||||
import llc.arma.ble.app.ui.common.ViewSideEffect
|
||||
import llc.arma.ble.app.ui.common.ViewState
|
||||
import llc.arma.ble.domain.model.BleInfo
|
||||
import javax.inject.Inject
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Refresh
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import com.patrykandpatrick.vico.compose.chart.column.columnChart
|
||||
import com.patrykandpatrick.vico.compose.chart.scroll.rememberChartScrollState
|
||||
import com.patrykandpatrick.vico.core.axis.AxisPosition
|
||||
import com.patrykandpatrick.vico.core.axis.formatter.AxisValueFormatter
|
||||
import com.patrykandpatrick.vico.core.entry.ChartEntry
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import llc.arma.ble.domain.common.ProgressState
|
||||
import llc.arma.ble.domain.model.Ble
|
||||
import llc.arma.ble.domain.usecase.GetAccelerometerHistoryBySerial
|
||||
import java.util.*
|
||||
|
||||
class AccelerometerEntry(
|
||||
val frequency: Long,
|
||||
override val x: Float,
|
||||
override val y: Float,
|
||||
) : ChartEntry {
|
||||
|
||||
override fun withY(y: Float) = AccelerometerEntry(frequency, x, y)
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AccelerometerHistory(
|
||||
ble: BleInfo
|
||||
) {
|
||||
|
||||
val viewModel = hiltViewModel<AccelerometerHistoryViewModel>()
|
||||
val state = viewModel.viewState.value
|
||||
|
||||
LaunchedEffect(ble.serial) {
|
||||
viewModel.setEvent(AccelerometerHistoryContract.Event.OnStart(ble.serial))
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxHeight(0.9f)
|
||||
) {
|
||||
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
|
||||
val title = when(state){
|
||||
is AccelerometerHistoryContract.State.Display -> {
|
||||
when (state.loadingHistoryState) {
|
||||
is ProgressState.Finished -> "История измерений (${state.loadingHistoryState.data.size})"
|
||||
is ProgressState.Indeterminate -> "История измерений"
|
||||
is ProgressState.Progress -> "История измерений"
|
||||
}
|
||||
}
|
||||
AccelerometerHistoryContract.State.Exception -> "История измерений"
|
||||
}
|
||||
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
|
||||
IconButton(
|
||||
onClick = {
|
||||
viewModel.setEvent(AccelerometerHistoryContract.Event.OnRefreshHistory(ble.serial))
|
||||
},
|
||||
enabled = when(state){
|
||||
is AccelerometerHistoryContract.State.Display -> state.loadingHistoryState is ProgressState.Finished
|
||||
AccelerometerHistoryContract.State.Exception -> true
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Refresh,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Box(modifier = Modifier) {
|
||||
|
||||
when (state) {
|
||||
is AccelerometerHistoryContract.State.Display -> Display(state = state)
|
||||
AccelerometerHistoryContract.State.Exception -> Exception()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun Display(
|
||||
state: AccelerometerHistoryContract.State.Display
|
||||
) {
|
||||
|
||||
Box(modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.fillMaxSize()
|
||||
) {
|
||||
|
||||
when (state.loadingHistoryState) {
|
||||
|
||||
is ProgressState.Finished -> {
|
||||
|
||||
if(state.loadingHistoryState.data.isEmpty()){
|
||||
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
text = "Нет данных"
|
||||
)
|
||||
|
||||
} else {
|
||||
|
||||
val producer = remember(state.loadingHistoryState.data) {
|
||||
state.loadingHistoryState.data.mapIndexed { index, measurePoint ->
|
||||
AccelerometerEntry(measurePoint.frequency, index.toFloat(), measurePoint.value)
|
||||
}.let {
|
||||
ChartEntryModelProducer(it)
|
||||
}
|
||||
}
|
||||
|
||||
val axisValueFormatter =
|
||||
AxisValueFormatter<AxisPosition.Horizontal.Bottom> { value, chartValues ->
|
||||
(chartValues.chartEntryModel.entries.firstOrNull()
|
||||
?.getOrNull(value.toInt()) as? AccelerometerEntry)
|
||||
?.frequency?.toString()
|
||||
.orEmpty()
|
||||
}
|
||||
|
||||
val lineChart = columnChart()
|
||||
|
||||
val scrollState = rememberChartScrollState()
|
||||
|
||||
Chart(
|
||||
chartScrollState = scrollState,
|
||||
chart = lineChart,
|
||||
chartModelProducer = producer,
|
||||
startAxis = startAxis(),
|
||||
bottomAxis = bottomAxis(
|
||||
tickLength = 0.dp,
|
||||
valueFormatter = axisValueFormatter,
|
||||
labelRotationDegrees = -90f,
|
||||
),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
|
||||
LaunchedEffect(scrollState.maxValue) {
|
||||
scrollState.scrollBy(scrollState.maxValue)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
is ProgressState.Indeterminate -> {
|
||||
|
||||
CircularProgressIndicator(
|
||||
strokeCap = StrokeCap.Round,
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
|
||||
}
|
||||
is ProgressState.Progress -> {
|
||||
|
||||
val progressAnimDuration = 1500
|
||||
val progressAnimation by animateFloatAsState(
|
||||
targetValue = state.loadingHistoryState.value,
|
||||
animationSpec = tween(
|
||||
durationMillis = progressAnimDuration,
|
||||
easing = FastOutSlowInEasing
|
||||
)
|
||||
)
|
||||
|
||||
CircularProgressIndicator(
|
||||
strokeCap = StrokeCap.Round,
|
||||
progress = progressAnimation,
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Exception(
|
||||
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(2f),
|
||||
){
|
||||
|
||||
Text(
|
||||
textAlign = TextAlign.Center,
|
||||
text = "Во время загрузки произошла ошибка",
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class AccelerometerHistoryContract {
|
||||
|
||||
sealed class Event : ViewEvent {
|
||||
|
||||
data class OnStart(
|
||||
val serial: String
|
||||
) : Event()
|
||||
|
||||
data class OnRefreshHistory(
|
||||
val serial: String
|
||||
) : Event()
|
||||
|
||||
}
|
||||
|
||||
sealed class State : ViewState {
|
||||
|
||||
data class Display(
|
||||
val loadingHistoryState : ProgressState<List<Ble.Accelerometer.MeasurePoint>>
|
||||
) : State()
|
||||
|
||||
object Exception : State()
|
||||
|
||||
}
|
||||
|
||||
sealed class Effect : ViewSideEffect {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@HiltViewModel
|
||||
class AccelerometerHistoryViewModel @Inject constructor(
|
||||
private val getAccelerometerHistoryBySerial: GetAccelerometerHistoryBySerial
|
||||
) : BaseViewModel<AccelerometerHistoryContract.State, AccelerometerHistoryContract.Event, AccelerometerHistoryContract.Effect>() {
|
||||
|
||||
private var lastSerial: String? = null
|
||||
|
||||
override fun setInitialState() = AccelerometerHistoryContract.State.Display(
|
||||
ProgressState.Indeterminate
|
||||
)
|
||||
|
||||
override fun handleEvents(event: AccelerometerHistoryContract.Event) {
|
||||
when(event){
|
||||
is AccelerometerHistoryContract.Event.OnStart -> reduce(viewState.value, event)
|
||||
is AccelerometerHistoryContract.Event.OnRefreshHistory -> reduce(viewState.value, event)
|
||||
}
|
||||
}
|
||||
|
||||
private fun reduce(
|
||||
state: AccelerometerHistoryContract.State,
|
||||
event: AccelerometerHistoryContract.Event.OnStart
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
|
||||
if(state is AccelerometerHistoryContract.State.Display) {
|
||||
|
||||
if(lastSerial != event.serial) {
|
||||
|
||||
lastSerial = event.serial
|
||||
|
||||
setState {
|
||||
AccelerometerHistoryContract.State.Display(ProgressState.Indeterminate)
|
||||
}
|
||||
|
||||
getAccelerometerHistoryBySerial(event.serial).onEach {
|
||||
it.fold(
|
||||
onSuccess = {
|
||||
setState {
|
||||
AccelerometerHistoryContract.State.Display(it)
|
||||
}
|
||||
},
|
||||
onFailure = {
|
||||
setState {
|
||||
AccelerometerHistoryContract.State.Exception
|
||||
}
|
||||
}
|
||||
)
|
||||
}.launchIn(this)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun reduce(
|
||||
state: AccelerometerHistoryContract.State,
|
||||
event: AccelerometerHistoryContract.Event.OnRefreshHistory
|
||||
) {
|
||||
viewModelScope.launch {
|
||||
|
||||
setState {
|
||||
AccelerometerHistoryContract.State.Display(ProgressState.Indeterminate)
|
||||
}
|
||||
|
||||
getAccelerometerHistoryBySerial(event.serial).onEach {
|
||||
it.fold(
|
||||
onSuccess = {
|
||||
setState {
|
||||
AccelerometerHistoryContract.State.Display(it)
|
||||
}
|
||||
},
|
||||
onFailure = {
|
||||
setState {
|
||||
AccelerometerHistoryContract.State.Exception
|
||||
}
|
||||
}
|
||||
)
|
||||
}.launchIn(this)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
|
|
@ -22,10 +23,13 @@ import llc.arma.ble.domain.model.BleInfo
|
|||
import javax.inject.Inject
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Refresh
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import com.patrykandpatrick.vico.compose.axis.horizontal.bottomAxis
|
||||
import com.patrykandpatrick.vico.compose.chart.column.columnChart
|
||||
import com.patrykandpatrick.vico.compose.chart.line.lineChart
|
||||
import com.patrykandpatrick.vico.compose.chart.scroll.rememberChartScrollSpec
|
||||
import com.patrykandpatrick.vico.core.chart.decoration.ThresholdLine
|
||||
import com.patrykandpatrick.vico.core.chart.scale.AutoScaleUp
|
||||
import com.patrykandpatrick.vico.core.entry.FloatEntry
|
||||
import com.patrykandpatrick.vico.core.scroll.AutoScrollCondition
|
||||
|
|
@ -33,6 +37,7 @@ import com.patrykandpatrick.vico.core.scroll.InitialScroll
|
|||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import llc.arma.ble.domain.usecase.Accelereate
|
||||
import llc.arma.ble.domain.usecase.GetAccelerometerMeasureBySerialFlow
|
||||
|
||||
@Composable
|
||||
|
|
@ -40,17 +45,21 @@ fun AccelerometerMeasure(
|
|||
ble: BleInfo
|
||||
) {
|
||||
|
||||
val viewModel = hiltViewModel<AccelerometerHistoryViewModel>()
|
||||
val viewModel = hiltViewModel<AccelerometerMeasureViewModel>()
|
||||
val state = viewModel.viewState.value
|
||||
|
||||
LaunchedEffect(ble.serial) {
|
||||
/*LaunchedEffect(ble.serial) {
|
||||
viewModel.setEvent(AccelerometerMeasureContract.Event.OnStart(ble.serial))
|
||||
}
|
||||
}*/
|
||||
|
||||
viewModel.setEvent(AccelerometerMeasureContract.Event.OnStart(ble.serial))
|
||||
|
||||
DisposableEffect(key1 = ble, effect = {
|
||||
|
||||
DisposableEffect(key1 = ble.serial, effect = {
|
||||
onDispose {
|
||||
viewModel.setEvent(AccelerometerMeasureContract.Event.StopMeasure)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
Column(
|
||||
|
|
@ -118,37 +127,104 @@ fun Display(
|
|||
|
||||
if (state.measureHistory.isEmpty()) {
|
||||
|
||||
Text(
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
text = "Нет данных"
|
||||
strokeCap = StrokeCap.Round
|
||||
)
|
||||
|
||||
} else {
|
||||
|
||||
val producer = remember {
|
||||
val xProducer = remember {
|
||||
ChartEntryModelProducer(listOf<FloatEntry>())
|
||||
}
|
||||
|
||||
producer.setEntries(state.measureHistory.mapIndexed { index, measurePoint ->
|
||||
FloatEntry(index.toFloat(), measurePoint)
|
||||
val yProducer = remember {
|
||||
ChartEntryModelProducer(listOf<FloatEntry>())
|
||||
}
|
||||
|
||||
val zProducer = remember {
|
||||
ChartEntryModelProducer(listOf<FloatEntry>())
|
||||
}
|
||||
|
||||
xProducer.setEntries(state.measureHistory.mapIndexed { index, measurePoint ->
|
||||
FloatEntry(index.toFloat(), measurePoint.x)
|
||||
})
|
||||
|
||||
val lineChart = columnChart()
|
||||
yProducer.setEntries(state.measureHistory.mapIndexed { index, measurePoint ->
|
||||
FloatEntry(index.toFloat(), measurePoint.y)
|
||||
})
|
||||
|
||||
Chart(
|
||||
chart = lineChart,
|
||||
chartModelProducer = producer,
|
||||
startAxis = startAxis(),
|
||||
bottomAxis = bottomAxis(),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
autoScaleUp = AutoScaleUp.None,
|
||||
diffAnimationSpec = tween(0),
|
||||
chartScrollSpec = rememberChartScrollSpec(
|
||||
initialScroll = InitialScroll.End,
|
||||
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased
|
||||
zProducer.setEntries(state.measureHistory.mapIndexed { index, measurePoint ->
|
||||
FloatEntry(index.toFloat(), measurePoint.z)
|
||||
})
|
||||
|
||||
val lineChart = lineChart(
|
||||
decorations = listOf(
|
||||
ThresholdLine(
|
||||
thresholdValue = 0f
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
Column() {
|
||||
|
||||
Text(text = "Ось X:")
|
||||
|
||||
Chart(
|
||||
chart = lineChart,
|
||||
chartModelProducer = xProducer,
|
||||
startAxis = startAxis(),
|
||||
bottomAxis = bottomAxis(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
autoScaleUp = AutoScaleUp.None,
|
||||
diffAnimationSpec = tween(0),
|
||||
chartScrollSpec = rememberChartScrollSpec(
|
||||
initialScroll = InitialScroll.End,
|
||||
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
|
||||
autoScrollAnimationSpec = tween(0)
|
||||
)
|
||||
)
|
||||
|
||||
Text(text = "Ось Y:")
|
||||
Chart(
|
||||
chart = lineChart,
|
||||
chartModelProducer = yProducer,
|
||||
startAxis = startAxis(),
|
||||
bottomAxis = bottomAxis(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
autoScaleUp = AutoScaleUp.None,
|
||||
diffAnimationSpec = tween(0),
|
||||
chartScrollSpec = rememberChartScrollSpec(
|
||||
initialScroll = InitialScroll.End,
|
||||
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
|
||||
autoScrollAnimationSpec = tween(0)
|
||||
)
|
||||
)
|
||||
|
||||
Text(text = "Ось Z:")
|
||||
Chart(
|
||||
chart = lineChart,
|
||||
chartModelProducer = zProducer,
|
||||
startAxis = startAxis(),
|
||||
bottomAxis = bottomAxis(),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f),
|
||||
autoScaleUp = AutoScaleUp.None,
|
||||
diffAnimationSpec = tween(0),
|
||||
chartScrollSpec = rememberChartScrollSpec(
|
||||
initialScroll = InitialScroll.End,
|
||||
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
|
||||
autoScrollAnimationSpec = tween(0)
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -156,7 +232,7 @@ fun Display(
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun Exception(
|
||||
private fun Exception(
|
||||
|
||||
) {
|
||||
Box(
|
||||
|
|
@ -195,7 +271,7 @@ class AccelerometerMeasureContract {
|
|||
sealed class State : ViewState {
|
||||
|
||||
data class Display(
|
||||
val measureHistory : List<Float>
|
||||
val measureHistory : List<Accelereate>
|
||||
) : State()
|
||||
|
||||
object Exception : State()
|
||||
|
|
@ -211,7 +287,7 @@ class AccelerometerMeasureContract {
|
|||
|
||||
|
||||
@HiltViewModel
|
||||
class AccelerometerHistoryViewModel @Inject constructor(
|
||||
class AccelerometerMeasureViewModel @Inject constructor(
|
||||
private val getAccelerometerMeasureBySerialFlow: GetAccelerometerMeasureBySerialFlow,
|
||||
) : BaseViewModel<AccelerometerMeasureContract.State, AccelerometerMeasureContract.Event, AccelerometerMeasureContract.Effect>() {
|
||||
|
||||
|
|
@ -237,6 +313,8 @@ class AccelerometerHistoryViewModel @Inject constructor(
|
|||
) {
|
||||
|
||||
measureJob?.cancel()
|
||||
measureJob = null
|
||||
|
||||
setState {
|
||||
AccelerometerMeasureContract.State.Display(emptyList())
|
||||
}
|
||||
|
|
@ -262,6 +340,8 @@ class AccelerometerHistoryViewModel @Inject constructor(
|
|||
private fun startReadMeasure(serial: String, restartJob: Boolean){
|
||||
|
||||
if(restartJob || measureJob == null) {
|
||||
measureJob?.cancel()
|
||||
measureJob = null
|
||||
measureJob = viewModelScope.launch {
|
||||
|
||||
setState {
|
||||
|
|
|
|||
|
|
@ -50,6 +50,47 @@ fun DisplayState(
|
|||
modifier = Modifier,
|
||||
content = {
|
||||
|
||||
Box(
|
||||
modifier = Modifier.padding(
|
||||
vertical = 8.dp,
|
||||
horizontal = 8.dp
|
||||
)
|
||||
) {
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.clickable {
|
||||
onEvent(AccelerometerContract.Event.OnPowerEdit)
|
||||
}
|
||||
.padding(8.dp)
|
||||
) {
|
||||
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
|
||||
Text(
|
||||
text = "Мощность"
|
||||
)
|
||||
Text(
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
text = "${ble.state.tx.value} db"
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.KeyboardArrowDown,
|
||||
contentDescription = null
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier.padding(
|
||||
vertical = 8.dp,
|
||||
|
|
@ -72,7 +113,43 @@ fun DisplayState(
|
|||
) {
|
||||
|
||||
Text(
|
||||
text = "График измерений"
|
||||
text = "График"
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.KeyboardArrowRight,
|
||||
contentDescription = null
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier.padding(
|
||||
vertical = 8.dp,
|
||||
horizontal = 8.dp
|
||||
)
|
||||
) {
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.clickable {
|
||||
onEvent(AccelerometerContract.Event.OnShowAccelerometerHistory)
|
||||
}
|
||||
.padding(8.dp)
|
||||
) {
|
||||
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
|
||||
Text(
|
||||
text = "История"
|
||||
)
|
||||
|
||||
}
|
||||
|
|
@ -99,7 +176,7 @@ fun DisplayState(
|
|||
shape = CircleShape,
|
||||
color = MaterialTheme.colorScheme.primaryContainer,
|
||||
onClick = {
|
||||
|
||||
onEvent(AccelerometerContract.Event.OnShowWriteBlePreview)
|
||||
}
|
||||
) {
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.unit.dp
|
||||
import llc.arma.ble.app.ui.model.BleView
|
||||
import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerContract
|
||||
import llc.arma.ble.app.ui.screen.inspection.thermometer.ThermometerContract
|
||||
|
||||
@Composable
|
||||
fun PowerEdit(
|
||||
state: BleView.Accelerometer,
|
||||
onEvent: (AccelerometerContract.Event) -> Unit,
|
||||
){
|
||||
|
||||
var value by remember(state.state.tx) {
|
||||
mutableStateOf(state.state.tx)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
) {
|
||||
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
text = "Мощность",
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
BleView.BleState.TX.values().forEach {
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.clickable { value = it }
|
||||
.padding(4.dp)
|
||||
) {
|
||||
|
||||
RadioButton(
|
||||
selected = it == value,
|
||||
onClick = { value = it }
|
||||
)
|
||||
|
||||
Text(text = it.value.toString() + " dBb (${it.powerPercentage} %)")
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp)
|
||||
.height(50.dp),
|
||||
shape = CircleShape,
|
||||
color = MaterialTheme.colorScheme.primaryContainer,
|
||||
onClick = {
|
||||
onEvent(
|
||||
AccelerometerContract.Event.OnPowerChanged(
|
||||
value
|
||||
)
|
||||
)
|
||||
}
|
||||
) {
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
color = MaterialTheme.colorScheme.background,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
text = "Применить"
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,349 @@
|
|||
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.launch
|
||||
import llc.arma.ble.R
|
||||
import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerContract
|
||||
import llc.arma.ble.app.ui.screen.inspection.thermometer.ThermometerContract
|
||||
import llc.arma.ble.app.ui.screen.inspection.thermometer.localizedName
|
||||
|
||||
@Composable
|
||||
fun Write(
|
||||
state: AccelerometerContract.State.Display.WriteState,
|
||||
onEvent: (AccelerometerContract.Event) -> Unit
|
||||
) {
|
||||
|
||||
Column(
|
||||
modifier = Modifier.animateContentSize()
|
||||
) {
|
||||
|
||||
Text(
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
text = "Запись изменений",
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
when (state) {
|
||||
is AccelerometerContract.State.Display.WriteState.DisplayPreview -> {
|
||||
|
||||
if(state.writeRequest.tx != null) {
|
||||
|
||||
state.writeRequest.tx?.let {
|
||||
Box(
|
||||
modifier = Modifier.padding(
|
||||
vertical = 0.dp,
|
||||
horizontal = 8.dp
|
||||
)
|
||||
) {
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.padding(8.dp)
|
||||
) {
|
||||
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
|
||||
Text(
|
||||
text = "Мощность"
|
||||
)
|
||||
Text(
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
text = "${it.localizedName} db"
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
Surface(
|
||||
shape = CircleShape,
|
||||
color = MaterialTheme.colorScheme.primaryContainer,
|
||||
onClick = {
|
||||
onEvent(AccelerometerContract.Event.OnWriteBle)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp)
|
||||
.height(50.dp),
|
||||
) {
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
color = MaterialTheme.colorScheme.background,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
text = "Записать"
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Surface(
|
||||
shape = CircleShape,
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
onClick = {
|
||||
onEvent(AccelerometerContract.Event.OnHideWriteBlePreview)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp)
|
||||
.height(50.dp),
|
||||
) {
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
text = "Отменить"
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
Spacer(modifier = Modifier.height(38.dp))
|
||||
|
||||
Text(
|
||||
text = "Нет изменений",
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(64.dp))
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp)
|
||||
.height(50.dp),
|
||||
shape = CircleShape,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
onClick = {
|
||||
onEvent(AccelerometerContract.Event.OnHideWriteBlePreview)
|
||||
}
|
||||
) {
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
color = MaterialTheme.colorScheme.onPrimary,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
text = "Ок"
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
is AccelerometerContract.State.Display.WriteState.Writing -> {
|
||||
|
||||
Box {
|
||||
|
||||
Column() {
|
||||
|
||||
Spacer(modifier = Modifier.height(28.dp))
|
||||
|
||||
CircularProgressIndicator(
|
||||
strokeCap = StrokeCap.Round,
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(48.dp))
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp)
|
||||
.height(50.dp),
|
||||
shape = CircleShape,
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
onClick = {
|
||||
onEvent(AccelerometerContract.Event.OnHideWriteBlePreview)
|
||||
}
|
||||
) {
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
text = "Отменить"
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
AccelerometerContract.State.Display.WriteState.Success -> {
|
||||
|
||||
Box {
|
||||
|
||||
Column {
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.size(125.dp)
|
||||
.align(Alignment.Center),
|
||||
painter = painterResource(llc.arma.ble.R.drawable.ic_done),
|
||||
contentDescription = null
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
text = "Успешно завершено"
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp)
|
||||
.height(50.dp),
|
||||
shape = CircleShape,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
onClick = {
|
||||
onEvent(AccelerometerContract.Event.OnHideWriteBlePreview)
|
||||
}
|
||||
) {
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
color = MaterialTheme.colorScheme.onPrimary,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
text = "Ок"
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
AccelerometerContract.State.Display.WriteState.Failure -> {
|
||||
|
||||
Box {
|
||||
|
||||
Column {
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.size(125.dp)
|
||||
.align(Alignment.Center),
|
||||
painter = painterResource(R.drawable.ic_error),
|
||||
contentDescription = null
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
text = "Ошибка записи"
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp)
|
||||
.height(50.dp),
|
||||
shape = CircleShape,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
onClick = {
|
||||
onEvent(AccelerometerContract.Event.OnHideWriteBlePreview)
|
||||
}
|
||||
) {
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
color = MaterialTheme.colorScheme.onPrimary,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
text = "Ок"
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -55,7 +55,7 @@ fun PowerEdit(
|
|||
onClick = { value = it }
|
||||
)
|
||||
|
||||
Text(text = it.value.toString() + " db")
|
||||
Text(text = it.value.toString() + " dBb (${it.powerPercentage} %)")
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ val Ble.BleState.TX.localizedName: String
|
|||
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun ThermometerScreen(
|
||||
ble: Ble.Thermometer,
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ fun PowerEdit(
|
|||
onClick = { value = it }
|
||||
)
|
||||
|
||||
Text(text = it.value.toString() + " db")
|
||||
Text(text = it.value.toString() + " dBb (${it.powerPercentage} %)")
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ fun Display(
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun Exception(
|
||||
private fun Exception(
|
||||
|
||||
) {
|
||||
Box(
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import llc.arma.ble.domain.model.Ble
|
|||
import llc.arma.ble.domain.model.BleInfo
|
||||
import llc.arma.ble.domain.model.ConnectedBleInfo
|
||||
import llc.arma.ble.domain.repository.BleRepository
|
||||
import llc.arma.ble.domain.usecase.Accelereate
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
|
@ -32,9 +33,9 @@ import kotlin.random.Random
|
|||
|
||||
val serviceUUID: UUID = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002")
|
||||
|
||||
val accelerometerDescriptorUUID: UUID = UUID.fromString("a77db6f2-9bc4-11ed-a8fc-0242ac120002")
|
||||
val accelerometerReadUUID: UUID = UUID.fromString("00002713-0000-1000-8000-00805f9b34fb")
|
||||
val temperatureHistoryReadUUID: UUID = UUID.fromString("a77db2d8-9bc4-11ed-a8fc-0242ac120002")
|
||||
val accelerometerHistoryReadUUID: UUID = UUID.fromString("a77db2d8-9bc4-11ed-a8fc-0242ac120002")
|
||||
val temperatureReadUUID: UUID = UUID.fromString("00002a6e-0000-1000-8000-00805f9b34fb")
|
||||
val intervalReadUUID: UUID = UUID.fromString("a77db2d8-9bc4-11ed-a8fc-0242ac120002")
|
||||
val intervalWriteUUID: UUID = UUID.fromString("a77db6f2-9bc4-11ed-a8fc-0242ac120002")
|
||||
|
|
@ -69,7 +70,8 @@ class BleRepositoryImpl @Inject constructor(
|
|||
batteryLevel = batteryLevel ?: 0,
|
||||
rssi = rssi,
|
||||
type = type,
|
||||
scanTime = timestampNanos / 1_000_000
|
||||
scanTime = timestampNanos / 1_000_000,
|
||||
tx = scanRecord?.txPowerLevel ?: 0
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -250,7 +252,19 @@ class BleRepositoryImpl @Inject constructor(
|
|||
} else {
|
||||
newResult.rssi
|
||||
}
|
||||
|
||||
),
|
||||
state = Ble.BleState(
|
||||
tx = when (result.scanRecord?.txPowerLevel) {
|
||||
-40 -> Ble.BleState.TX.MINUS_40
|
||||
-20 -> Ble.BleState.TX.MINUS_20
|
||||
-16 -> Ble.BleState.TX.MINUS_16
|
||||
-12 -> Ble.BleState.TX.MINUS_12
|
||||
-8 -> Ble.BleState.TX.MINUS_8
|
||||
-4 -> Ble.BleState.TX.MINUS_4
|
||||
3 -> Ble.BleState.TX.PLUS_3
|
||||
4 -> Ble.BleState.TX.PLUS_4
|
||||
else -> Ble.BleState.TX.ZERO
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -484,6 +498,44 @@ class BleRepositoryImpl @Inject constructor(
|
|||
|
||||
}
|
||||
|
||||
override suspend fun getAccelerometerHistoryBySerial(
|
||||
serial: String
|
||||
): Flow<Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>> {
|
||||
|
||||
var gatt: BluetoothGatt? = null
|
||||
|
||||
return callbackFlow {
|
||||
|
||||
deviceCache[serial]?.device?.let {
|
||||
|
||||
if (checkPermission()) {
|
||||
|
||||
gatt = it.connectGatt(app, false, ReadAccelerometerHistoryCallback(app) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
send(it)
|
||||
}
|
||||
})
|
||||
|
||||
} else {
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
send(Result.failure(BleException.PermissionDenied))
|
||||
}
|
||||
|
||||
return@callbackFlow
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
awaitClose {
|
||||
gatt?.close()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override suspend fun getTemperatureHistoryBySerial(
|
||||
serial: String
|
||||
): Flow<Result<ProgressState<List<Ble.Thermometer.MeasurePoint>>, BleException>> {
|
||||
|
|
@ -594,6 +646,42 @@ class BleRepositoryImpl @Inject constructor(
|
|||
|
||||
}
|
||||
|
||||
override suspend fun writeBle(
|
||||
serial: String,
|
||||
request: Ble.Accelerometer.WriteRequest
|
||||
): Result<Unit, BleException> = suspendCancellableCoroutine {
|
||||
|
||||
deviceCache[serial]?.let { scanResult ->
|
||||
|
||||
if(checkPermission()) {
|
||||
|
||||
var gatt: BluetoothGatt? = null
|
||||
|
||||
val callback = WriteAccelerometerCallback(app, request) { result ->
|
||||
|
||||
gatt?.close()
|
||||
|
||||
result.onSuccess {
|
||||
deviceCache.remove(serial)
|
||||
resultList.remove(serial)
|
||||
}
|
||||
|
||||
it.resume(result)
|
||||
|
||||
}
|
||||
|
||||
gatt = scanResult.device.connectGatt(app, false, callback)
|
||||
|
||||
} else {
|
||||
|
||||
it.resume(Result.failure(BleException.PermissionDenied))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override suspend fun changeBlePassword(
|
||||
password: String,
|
||||
serial: String
|
||||
|
|
@ -618,53 +706,28 @@ class BleRepositoryImpl @Inject constructor(
|
|||
return Result.success(Unit)
|
||||
}
|
||||
|
||||
override fun getAccelerometerMeasureBySerialFlow(serial: String): Flow<Result<Float, BleException>> {
|
||||
override fun getAccelerometerMeasureBySerialFlow(serial: String): Flow<Result<Accelereate, BleException>> {
|
||||
|
||||
return callbackFlow {
|
||||
|
||||
var gatt: BluetoothGatt? = null
|
||||
|
||||
deviceCache[serial]?.let {
|
||||
|
||||
it.device.connectGatt(app, false, object : BluetoothGattCallback() {
|
||||
|
||||
override fun onConnectionStateChange(
|
||||
gatt: BluetoothGatt,
|
||||
status: Int,
|
||||
newState: Int
|
||||
) {
|
||||
gatt.discoverServices()
|
||||
}
|
||||
|
||||
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
|
||||
super.onServicesDiscovered(gatt, status)
|
||||
gatt.getService(serviceUUID)?.getCharacteristic(accelerometerReadUUID)?.let {
|
||||
gatt.setCharacteristicNotification(it, true)
|
||||
gatt.writeDescriptor(it.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")), BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCharacteristicChanged(
|
||||
gatt: BluetoothGatt,
|
||||
characteristic: BluetoothGattCharacteristic,
|
||||
value: ByteArray
|
||||
) {
|
||||
|
||||
Log.d("new", value.toString())
|
||||
|
||||
gatt = it.device.connectGatt(
|
||||
app,
|
||||
false,
|
||||
ReadAccelerometerCallback(app){ result ->
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
|
||||
send(
|
||||
Result.success(((-256)..256).random().toFloat())
|
||||
)
|
||||
|
||||
send(result)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
)
|
||||
|
||||
}
|
||||
awaitClose {
|
||||
|
||||
Log.d("acc", "close")
|
||||
gatt?.close()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,153 @@
|
|||
package llc.arma.ble.data
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Application
|
||||
import android.bluetooth.BluetoothGatt
|
||||
import android.bluetooth.BluetoothGattCallback
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
import android.bluetooth.BluetoothGattDescriptor
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.core.app.ActivityCompat
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import llc.arma.ble.domain.Result
|
||||
import llc.arma.ble.domain.common.BleException
|
||||
import llc.arma.ble.domain.common.ProgressState
|
||||
import llc.arma.ble.domain.model.Ble
|
||||
import llc.arma.ble.domain.usecase.Accelereate
|
||||
import java.util.UUID
|
||||
import java.util.stream.Collectors
|
||||
|
||||
|
||||
|
||||
class ReadAccelerometerCallback(
|
||||
private val app: Application,
|
||||
private val onResult: (Result<Accelereate, BleException>) -> Unit
|
||||
) : BluetoothGattCallback() {
|
||||
|
||||
override fun onConnectionStateChange(
|
||||
gatt: BluetoothGatt,
|
||||
status: Int,
|
||||
newState: Int
|
||||
) {
|
||||
super.onConnectionStateChange(gatt, status, newState)
|
||||
|
||||
if(status == BluetoothGatt.GATT_SUCCESS){
|
||||
|
||||
if(newState == BluetoothGatt.STATE_CONNECTED){
|
||||
|
||||
if (checkPermission()) {
|
||||
gatt.discoverServices()
|
||||
|
||||
} else {
|
||||
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||
gatt.close()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||
gatt.close()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onServicesDiscovered(
|
||||
gatt: BluetoothGatt,
|
||||
status: Int
|
||||
) {
|
||||
super.onServicesDiscovered(gatt, status)
|
||||
|
||||
if(status == BluetoothGatt.GATT_SUCCESS){
|
||||
|
||||
gatt.getService(serviceUUID)?.getCharacteristic(accelerometerReadUUID)?.let {
|
||||
|
||||
if (checkPermission()) {
|
||||
|
||||
gatt.setCharacteristicNotification(it, true)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
gatt.writeDescriptor(it.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")), BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)
|
||||
} else {
|
||||
val descriptor = it.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"))
|
||||
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
|
||||
gatt.writeDescriptor(descriptor)
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
onResult(Result.failure(BleException.PermissionDenied))
|
||||
gatt.close()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onCharacteristicChanged(
|
||||
gatt: BluetoothGatt,
|
||||
characteristic: BluetoothGattCharacteristic
|
||||
) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
super.onCharacteristicChanged(gatt, characteristic)
|
||||
onCommonCharacteristicRead(gatt, characteristic, characteristic.value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCharacteristicChanged(
|
||||
gatt: BluetoothGatt,
|
||||
characteristic: BluetoothGattCharacteristic,
|
||||
value: ByteArray
|
||||
) {
|
||||
super.onCharacteristicChanged(gatt, characteristic, value)
|
||||
onCommonCharacteristicRead(gatt, characteristic, value)
|
||||
}
|
||||
|
||||
|
||||
private fun onCommonCharacteristicRead(
|
||||
gatt: BluetoothGatt,
|
||||
characteristic: BluetoothGattCharacteristic,
|
||||
value: ByteArray,
|
||||
){
|
||||
|
||||
val data = value.toList().chunked(2).map {
|
||||
it[1].toInt()
|
||||
}
|
||||
|
||||
onResult(
|
||||
Result.success(
|
||||
Accelereate(
|
||||
x = data[0].toFloat(),
|
||||
y = data[1].toFloat(),
|
||||
z = data[2].toFloat()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
private fun checkPermission(): Boolean {
|
||||
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_CONNECT) ==
|
||||
PackageManager.PERMISSION_GRANTED &&
|
||||
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_SCAN) ==
|
||||
PackageManager.PERMISSION_GRANTED
|
||||
} else {
|
||||
return ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_FINE_LOCATION) ==
|
||||
PackageManager.PERMISSION_GRANTED &&
|
||||
ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_COARSE_LOCATION) ==
|
||||
PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,342 @@
|
|||
package llc.arma.ble.data
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Application
|
||||
import android.bluetooth.BluetoothGatt
|
||||
import android.bluetooth.BluetoothGattCallback
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.core.app.ActivityCompat
|
||||
import llc.arma.ble.domain.Result
|
||||
import llc.arma.ble.domain.common.BleException
|
||||
import llc.arma.ble.domain.common.ProgressState
|
||||
import llc.arma.ble.domain.model.Ble
|
||||
|
||||
class ReadAccelerometerHistoryCallback(
|
||||
private val app: Application,
|
||||
private val onResult: (Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>) -> Unit
|
||||
) : BluetoothGattCallback() {
|
||||
|
||||
private fun ByteArray.get4byteUIntAt(idx: Int) =
|
||||
((this[idx + 3].toUInt() and 0xFFu) shl 24) or
|
||||
((this[idx + 2].toUInt() and 0xFFu) shl 16) or
|
||||
((this[idx + 1].toUInt() and 0xFFu) shl 8) or
|
||||
(this[idx].toUInt() and 0xFFu)
|
||||
|
||||
private fun ByteArray.get2byteUIntAt(idx: Int) =
|
||||
((this[idx + 1].toUInt() and 0xFFu) shl 8) or
|
||||
(this[idx].toUInt() and 0xFFu)
|
||||
|
||||
private var readProperty: Property? = null
|
||||
|
||||
init {
|
||||
onResult(Result.success(ProgressState.Indeterminate))
|
||||
}
|
||||
|
||||
override fun onConnectionStateChange(
|
||||
gatt: BluetoothGatt,
|
||||
status: Int,
|
||||
newState: Int
|
||||
) {
|
||||
super.onConnectionStateChange(gatt, status, newState)
|
||||
|
||||
if(status == BluetoothGatt.GATT_SUCCESS){
|
||||
|
||||
if(newState == BluetoothGatt.STATE_CONNECTED){
|
||||
|
||||
if (checkPermission()) {
|
||||
gatt.discoverServices()
|
||||
|
||||
} else {
|
||||
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||
gatt.close()
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||
gatt.close()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onServicesDiscovered(
|
||||
gatt: BluetoothGatt,
|
||||
status: Int
|
||||
) {
|
||||
super.onServicesDiscovered(gatt, status)
|
||||
Log.d("read", "discover ${status}")
|
||||
if(status == BluetoothGatt.GATT_SUCCESS){
|
||||
gatt.getService(serviceUUID)?.getCharacteristic(accelerometerHistoryReadUUID)?.let {
|
||||
|
||||
if (checkPermission()) {
|
||||
|
||||
Log.d("read", "discovered")
|
||||
|
||||
readProperty = Property.DATA_SIZE
|
||||
gatt.writeCharacteristic(it, byteArrayOf(2))
|
||||
|
||||
} else {
|
||||
|
||||
onResult(Result.failure(BleException.PermissionDenied))
|
||||
gatt.close()
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private var lastMeasureSystemTime: Long? = null
|
||||
|
||||
private var bleMeasureInterval: Long? = null
|
||||
private var bleRealTime: Long? = null
|
||||
private var bleLastMeasureTime: Long? = null
|
||||
|
||||
private val resultAccelerometerPackage: MutableList<Float> = mutableListOf()
|
||||
|
||||
var expectedDataSize: Int? = null
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun onCharacteristicRead(
|
||||
gatt: BluetoothGatt,
|
||||
characteristic: BluetoothGattCharacteristic,
|
||||
status: Int
|
||||
) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
super.onCharacteristicRead(gatt, characteristic, status)
|
||||
onCommonCharacteristicRead(gatt, characteristic, characteristic.value, status)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCharacteristicRead(
|
||||
gatt: BluetoothGatt,
|
||||
characteristic: BluetoothGattCharacteristic,
|
||||
value: ByteArray,
|
||||
status: Int
|
||||
) {
|
||||
super.onCharacteristicRead(gatt, characteristic, value, status)
|
||||
onCommonCharacteristicRead(gatt, characteristic, value, status)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
private fun onCommonCharacteristicRead(
|
||||
gatt: BluetoothGatt,
|
||||
characteristic: BluetoothGattCharacteristic,
|
||||
value: ByteArray,
|
||||
status: Int
|
||||
){
|
||||
Log.d("read", value[0].toString())
|
||||
|
||||
if(status == BluetoothGatt.GATT_SUCCESS){
|
||||
when(readProperty){
|
||||
Property.DATA_SIZE -> {
|
||||
|
||||
if(value.contentEquals(byteArrayOf(0, 0))) {
|
||||
onResult(
|
||||
Result.success(
|
||||
ProgressState.Finished(
|
||||
emptyList()
|
||||
)
|
||||
)
|
||||
)
|
||||
gatt.close()
|
||||
|
||||
} else {
|
||||
|
||||
val writeData = mutableListOf(
|
||||
1.toByte(),
|
||||
0.toByte(),
|
||||
0.toByte()
|
||||
).apply {
|
||||
addAll(value.toList())
|
||||
}.toByteArray()
|
||||
|
||||
readProperty = Property.PACKAGE
|
||||
gatt.writeCharacteristic(characteristic, writeData)
|
||||
|
||||
}
|
||||
}
|
||||
Property.PACKAGE -> {
|
||||
|
||||
if(value[0] == 250.toByte()){
|
||||
|
||||
bleMeasureInterval = value.get4byteUIntAt(4).toLong()
|
||||
bleLastMeasureTime = value.get4byteUIntAt(8).toLong()
|
||||
bleRealTime = value.get4byteUIntAt(12).toLong()
|
||||
|
||||
lastMeasureSystemTime = ((bleRealTime!! - bleLastMeasureTime!!) * 1_000)
|
||||
|
||||
val accelerometerDataArray = value.toUByteArray().asList().subList(16, value.size)
|
||||
|
||||
resultAccelerometerPackage.addAll(
|
||||
accelerometerDataArray.chunked(2).map {
|
||||
it.toUByteArray().toTemperature()
|
||||
}.toMutableList()
|
||||
)
|
||||
|
||||
val nextPackageDataCount = value.get2byteUIntAt(2)
|
||||
expectedDataSize = nextPackageDataCount.toInt() + resultAccelerometerPackage.size
|
||||
Log.d("read", expectedDataSize.toString())
|
||||
onResult(Result.success(ProgressState.Progress(0f / expectedDataSize!!.toFloat())))
|
||||
onResult(Result.success(ProgressState.Progress(resultAccelerometerPackage.size.toFloat() / expectedDataSize!!.toFloat())))
|
||||
|
||||
if(nextPackageDataCount != 0.toUInt()){
|
||||
|
||||
if (checkPermission()) {
|
||||
|
||||
gatt.writeCharacteristic(characteristic, byteArrayOf(5))
|
||||
gatt.readCharacteristic(characteristic)
|
||||
|
||||
} else {
|
||||
|
||||
onResult(Result.failure(BleException.PermissionDenied))
|
||||
gatt.close()
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
onResult(
|
||||
Result.success(
|
||||
ProgressState.Finished(
|
||||
resultAccelerometerPackage.withIndex().map {
|
||||
Ble.Accelerometer.MeasurePoint(
|
||||
frequency = lastMeasureSystemTime!! - (((resultAccelerometerPackage.size - 1) - it.index) * bleMeasureInterval!!),
|
||||
value = it.value
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
gatt.close()
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (value[0] == 251.toByte()) {
|
||||
|
||||
val nextPackageDataCount = value.get2byteUIntAt(2)
|
||||
val temperatureDataArray = value.toUByteArray().toList().subList(4, value.size)
|
||||
|
||||
resultAccelerometerPackage.addAll(
|
||||
temperatureDataArray.chunked(2).map {
|
||||
it.toUByteArray().toTemperature()
|
||||
}
|
||||
)
|
||||
|
||||
onResult(Result.success(ProgressState.Progress(resultAccelerometerPackage.size.toFloat() / expectedDataSize!!.toFloat())))
|
||||
|
||||
if (nextPackageDataCount != 0.toUInt()) {
|
||||
|
||||
val writeData = byteArrayOf(5)
|
||||
|
||||
gatt.writeCharacteristic(characteristic, writeData)
|
||||
gatt.readCharacteristic(characteristic)
|
||||
|
||||
} else {
|
||||
onResult(
|
||||
Result.success(
|
||||
ProgressState.Finished(
|
||||
resultAccelerometerPackage.withIndex().map {
|
||||
Ble.Accelerometer.MeasurePoint(
|
||||
frequency = lastMeasureSystemTime!! - (((resultAccelerometerPackage.size - 1) - it.index) * bleMeasureInterval!!),
|
||||
value = it.value
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
gatt.close()
|
||||
}
|
||||
} else {
|
||||
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||
gatt.close()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||
gatt.close()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCharacteristicWrite(
|
||||
gatt: BluetoothGatt,
|
||||
characteristic: BluetoothGattCharacteristic,
|
||||
status: Int
|
||||
) {
|
||||
super.onCharacteristicWrite(gatt, characteristic, status)
|
||||
if(status == BluetoothGatt.GATT_SUCCESS){
|
||||
|
||||
if (checkPermission()) {
|
||||
|
||||
gatt.readCharacteristic(characteristic)
|
||||
|
||||
} else {
|
||||
|
||||
onResult(Result.failure(BleException.PermissionDenied))
|
||||
gatt.close()
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||
gatt.close()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun checkPermission(): Boolean {
|
||||
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_CONNECT) ==
|
||||
PackageManager.PERMISSION_GRANTED &&
|
||||
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_SCAN) ==
|
||||
PackageManager.PERMISSION_GRANTED
|
||||
} else {
|
||||
return ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_FINE_LOCATION) ==
|
||||
PackageManager.PERMISSION_GRANTED &&
|
||||
ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_COARSE_LOCATION) ==
|
||||
PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
}
|
||||
|
||||
fun BluetoothGatt.writeCharacteristic(
|
||||
characteristic: BluetoothGattCharacteristic,
|
||||
data: ByteArray
|
||||
): Result<Unit, BleException>{
|
||||
|
||||
return if(checkPermission()){
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
writeCharacteristic(characteristic, data, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
|
||||
}else{
|
||||
characteristic.value = data
|
||||
writeCharacteristic(characteristic)
|
||||
}
|
||||
|
||||
Result.success(Unit)
|
||||
|
||||
} else {
|
||||
Result.failure(BleException.PermissionDenied)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
package llc.arma.ble.data
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Application
|
||||
import android.bluetooth.BluetoothGatt
|
||||
import android.bluetooth.BluetoothGattCallback
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
import android.bluetooth.BluetoothProfile
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.core.app.ActivityCompat
|
||||
import llc.arma.ble.domain.Result
|
||||
import llc.arma.ble.domain.common.BleException
|
||||
import llc.arma.ble.domain.model.Ble
|
||||
import java.util.UUID
|
||||
|
||||
class WriteAccelerometerCallback(
|
||||
private val app: Application,
|
||||
private var request: Ble.Accelerometer.WriteRequest,
|
||||
private val onResult: (Result<Unit, BleException>) -> Unit
|
||||
) : BluetoothGattCallback() {
|
||||
|
||||
private var flashed = false
|
||||
|
||||
override fun onConnectionStateChange(
|
||||
gatt: BluetoothGatt,
|
||||
status: Int,
|
||||
newState: Int
|
||||
) {
|
||||
super.onConnectionStateChange(gatt, status, newState)
|
||||
|
||||
if(checkPermission()) {
|
||||
|
||||
if(status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
|
||||
|
||||
gatt.discoverServices()
|
||||
|
||||
} else {
|
||||
|
||||
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
onResult(Result.failure(BleException.PermissionDenied))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onServicesDiscovered(
|
||||
gatt: BluetoothGatt,
|
||||
status: Int
|
||||
) {
|
||||
super.onServicesDiscovered(gatt, status)
|
||||
onCycle(gatt, status)
|
||||
|
||||
}
|
||||
|
||||
private fun onCycle(
|
||||
gatt: BluetoothGatt,
|
||||
status: Int
|
||||
){
|
||||
|
||||
if(request.tx != null) {
|
||||
|
||||
var uuid: Pair<UUID, ByteArray>? = null
|
||||
|
||||
uuid = request.tx?.let {
|
||||
|
||||
this.request = request.copy(
|
||||
tx = null
|
||||
)
|
||||
|
||||
Pair(
|
||||
txWriteUUID,
|
||||
byteArrayOf(
|
||||
when (it) {
|
||||
Ble.BleState.TX.MINUS_40 -> -40
|
||||
Ble.BleState.TX.MINUS_20 -> -20
|
||||
Ble.BleState.TX.MINUS_16 -> -16
|
||||
Ble.BleState.TX.MINUS_12 -> -12
|
||||
Ble.BleState.TX.MINUS_8 -> -8
|
||||
Ble.BleState.TX.MINUS_4 -> -4
|
||||
Ble.BleState.TX.ZERO -> 0
|
||||
Ble.BleState.TX.PLUS_3 -> 3
|
||||
Ble.BleState.TX.PLUS_4 -> 4
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
} ?: uuid
|
||||
|
||||
uuid?.let { uuid ->
|
||||
|
||||
gatt.services.firstOrNull { it.uuid == serviceUUID }?.characteristics?.firstOrNull {
|
||||
it.uuid == uuid.first
|
||||
}?.let {
|
||||
|
||||
gatt.writeCharacteristic(it, uuid.second)
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||
|
||||
} else {
|
||||
|
||||
if(flashed.not()){
|
||||
|
||||
flashed = true
|
||||
|
||||
gatt.services.firstOrNull { it.uuid == serviceUUID }?.characteristics?.firstOrNull {
|
||||
it.uuid == flashWriteUUID
|
||||
}?.let {
|
||||
|
||||
gatt.writeCharacteristic(it, byteArrayOf(9))
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||
|
||||
} else {
|
||||
|
||||
onResult(Result.success(Unit))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onCharacteristicWrite(
|
||||
gatt: BluetoothGatt,
|
||||
characteristic: BluetoothGattCharacteristic,
|
||||
status: Int
|
||||
) {
|
||||
|
||||
Log.d("beacon", "onCharacteristicWrite $status")
|
||||
|
||||
super.onCharacteristicWrite(gatt, characteristic, status)
|
||||
|
||||
if(checkPermission()) {
|
||||
|
||||
if(status == BluetoothGatt.GATT_SUCCESS || flashed) {
|
||||
|
||||
onCycle(gatt, status)
|
||||
|
||||
} else {
|
||||
|
||||
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
onResult(Result.failure(BleException.PermissionDenied))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun BluetoothGatt.writeCharacteristic(
|
||||
characteristic: BluetoothGattCharacteristic,
|
||||
data: ByteArray
|
||||
): Result<Unit, BleException> {
|
||||
|
||||
return if(checkPermission()){
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
writeCharacteristic(characteristic, data, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
|
||||
}else{
|
||||
|
||||
characteristic.writeType
|
||||
characteristic.value = data
|
||||
writeCharacteristic(characteristic)
|
||||
}
|
||||
|
||||
Result.success(Unit)
|
||||
|
||||
} else {
|
||||
Result.failure(BleException.PermissionDenied)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun checkPermission(): Boolean {
|
||||
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_CONNECT) ==
|
||||
PackageManager.PERMISSION_GRANTED &&
|
||||
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_SCAN) ==
|
||||
PackageManager.PERMISSION_GRANTED
|
||||
} else {
|
||||
return ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_FINE_LOCATION) ==
|
||||
PackageManager.PERMISSION_GRANTED &&
|
||||
ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_COARSE_LOCATION) ==
|
||||
PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -30,8 +30,6 @@ class WriteBeaconCallback(
|
|||
) {
|
||||
super.onConnectionStateChange(gatt, status, newState)
|
||||
|
||||
Log.d("beacon", "onConnectionStateChange $status $newState")
|
||||
|
||||
if(checkPermission()) {
|
||||
|
||||
if(status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
|
||||
|
|
@ -56,7 +54,6 @@ class WriteBeaconCallback(
|
|||
gatt: BluetoothGatt,
|
||||
status: Int
|
||||
) {
|
||||
Log.d("beacon", "onServicesDiscovered $status")
|
||||
super.onServicesDiscovered(gatt, status)
|
||||
onCycle(gatt, status)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,18 @@ sealed class Ble(
|
|||
) {
|
||||
|
||||
class Accelerometer(
|
||||
info: BleInfo
|
||||
): Ble(info){
|
||||
info: BleInfo,
|
||||
val state: BleState
|
||||
): Ble(info) {
|
||||
|
||||
data class WriteRequest(
|
||||
val tx: BleState.TX?,
|
||||
)
|
||||
|
||||
class MeasurePoint (
|
||||
val frequency: Long,
|
||||
val value: Float
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ data class BleInfo(
|
|||
val batteryLevel: Int,
|
||||
val rssi: Int?,
|
||||
val type: Type,
|
||||
val scanTime: Long
|
||||
val scanTime: Long,
|
||||
val tx: Int
|
||||
){
|
||||
|
||||
enum class Type {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import llc.arma.ble.domain.common.ProgressState
|
|||
import llc.arma.ble.domain.model.Ble
|
||||
import llc.arma.ble.domain.model.BleInfo
|
||||
import llc.arma.ble.domain.model.ConnectedBleInfo
|
||||
import llc.arma.ble.domain.usecase.Accelereate
|
||||
import llc.arma.ble.domain.usecase.GetBleBySerial
|
||||
|
||||
interface BleRepository {
|
||||
|
|
@ -23,7 +24,12 @@ interface BleRepository {
|
|||
|
||||
suspend fun writeBle(serial: String, request: Ble.Beacon.WriteRequest): Result<Unit, BleException>
|
||||
|
||||
suspend fun writeBle(serial: String, request: Ble.Accelerometer.WriteRequest): Result<Unit, BleException>
|
||||
|
||||
suspend fun changeBlePassword(password: String, serial: String): Result<Unit, BleException>
|
||||
fun getAccelerometerMeasureBySerialFlow(serial: String): Flow<Result<Float, BleException>>
|
||||
|
||||
fun getAccelerometerMeasureBySerialFlow(serial: String): Flow<Result<Accelereate, BleException>>
|
||||
|
||||
suspend fun getAccelerometerHistoryBySerial(serial: String): Flow<Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>>
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package llc.arma.ble.domain.usecase
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import llc.arma.ble.domain.Result
|
||||
import llc.arma.ble.domain.common.BleException
|
||||
import llc.arma.ble.domain.common.ProgressState
|
||||
import llc.arma.ble.domain.model.Ble
|
||||
import llc.arma.ble.domain.repository.BleRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetAccelerometerHistoryBySerial @Inject constructor(
|
||||
private val bleRepository: BleRepository
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(serial: String): Flow<Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>> {
|
||||
|
||||
return bleRepository.getAccelerometerHistoryBySerial(serial)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -12,10 +12,16 @@ class GetAccelerometerMeasureBySerialFlow @Inject constructor(
|
|||
private val bleRepository: BleRepository
|
||||
) {
|
||||
|
||||
operator fun invoke(serial: String): Flow<Result<Float, BleException>> {
|
||||
operator fun invoke(serial: String): Flow<Result<Accelereate, BleException>> {
|
||||
|
||||
return bleRepository.getAccelerometerMeasureBySerialFlow(serial)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data class Accelereate(
|
||||
val x: Float,
|
||||
val y: Float,
|
||||
val z: Float,
|
||||
)
|
||||
|
|
@ -24,4 +24,11 @@ class WriteBle @Inject constructor(
|
|||
return bleRepository.writeBle(serial, request)
|
||||
}
|
||||
|
||||
suspend operator fun invoke(
|
||||
serial: String,
|
||||
request: Ble.Accelerometer.WriteRequest
|
||||
): llc.arma.ble.domain.Result<Unit, BleException>{
|
||||
return bleRepository.writeBle(serial, request)
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue