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"
|
applicationId "llc.arma.ble"
|
||||||
minSdk 24
|
minSdk 24
|
||||||
targetSdk 33
|
targetSdk 33
|
||||||
versionCode 1
|
versionCode 2
|
||||||
versionName "1.0"
|
versionName "1.1"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,10 @@ class MainActivity : ComponentActivity() {
|
||||||
mutableStateOf<@Composable () -> Unit>({})
|
mutableStateOf<@Composable () -> Unit>({})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(modalState.currentValue == ModalBottomSheetValue.Hidden){
|
||||||
|
sheetContent = {}
|
||||||
|
}
|
||||||
|
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalBottomDialogState provides BottomState(
|
LocalBottomDialogState provides BottomState(
|
||||||
sheetState = modalState,
|
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 -> {
|
is Ble.Accelerometer -> {
|
||||||
BleView.Accelerometer(
|
BleView.Accelerometer(
|
||||||
info = input.info,
|
info = input.info,
|
||||||
|
state = BleView.BleState(
|
||||||
|
tx = txMapper.map(input.state.tx)
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,10 @@ class BleViewMapper @Inject constructor(
|
||||||
|
|
||||||
is BleView.Accelerometer -> {
|
is BleView.Accelerometer -> {
|
||||||
Ble.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(
|
class Accelerometer(
|
||||||
info: BleInfo
|
info: BleInfo,
|
||||||
|
val state: BleState
|
||||||
) : BleView(info)
|
) : BleView(info)
|
||||||
|
|
||||||
class Beacon(
|
class Beacon(
|
||||||
|
|
@ -58,9 +59,27 @@ sealed class BleView(
|
||||||
MINUS_4(-4),
|
MINUS_4(-4),
|
||||||
ZERO(0),
|
ZERO(0),
|
||||||
PLUS_3(3),
|
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.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableLongStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
|
@ -29,9 +30,11 @@ import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import llc.arma.ble.app.ui.common.SignalLevel
|
||||||
import llc.arma.ble.app.ui.common.rememberBottomDialogState
|
import llc.arma.ble.app.ui.common.rememberBottomDialogState
|
||||||
import llc.arma.ble.domain.model.BleInfo
|
import llc.arma.ble.domain.model.BleInfo
|
||||||
import llc.arma.ble.domain.model.ConnectedBleInfo
|
import llc.arma.ble.domain.model.ConnectedBleInfo
|
||||||
|
import kotlin.math.pow
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@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
|
@Composable
|
||||||
private fun BleItem(
|
private fun BleItem(
|
||||||
ble: BleInfo,
|
ble: BleInfo,
|
||||||
|
|
@ -254,6 +267,41 @@ private fun BleItem(
|
||||||
text = ble.serial
|
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(
|
Row(
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
|
|
@ -263,12 +311,10 @@ private fun BleItem(
|
||||||
modifier = Modifier.alpha(0.7f)
|
modifier = Modifier.alpha(0.7f)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Icon(
|
SignalLevel(level = ble.rssi?.toSignalLevel() ?: 0)
|
||||||
modifier = Modifier.size(16.dp),
|
|
||||||
imageVector = Icons.Rounded.NetworkCell,
|
|
||||||
contentDescription = null
|
|
||||||
)
|
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,26 @@ class AccelerometerContract {
|
||||||
|
|
||||||
object OnHideAccelerometerMeasure : Event()
|
object OnHideAccelerometerMeasure : Event()
|
||||||
|
|
||||||
|
object OnShowAccelerometerHistory : Event()
|
||||||
|
|
||||||
|
object OnHideAccelerometerHistory : Event()
|
||||||
|
|
||||||
|
object OnPowerEdit : Event()
|
||||||
|
|
||||||
|
object OnShowWriteBlePreview : Event()
|
||||||
|
|
||||||
|
object OnWriteBle : Event()
|
||||||
|
|
||||||
|
object OnHideWriteBlePreview : Event()
|
||||||
|
|
||||||
data class OnBleChanged(
|
data class OnBleChanged(
|
||||||
val ble: Ble.Accelerometer,
|
val ble: Ble.Accelerometer,
|
||||||
): Event()
|
): Event()
|
||||||
|
|
||||||
|
data class OnPowerChanged(
|
||||||
|
val tx: BleView.BleState.TX
|
||||||
|
) : Event()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class State : ViewState {
|
sealed class State : ViewState {
|
||||||
|
|
@ -28,7 +44,26 @@ class AccelerometerContract {
|
||||||
data class Display(
|
data class Display(
|
||||||
val origin: Ble.Accelerometer,
|
val origin: Ble.Accelerometer,
|
||||||
val accelerometer: BleView.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 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
|
package llc.arma.ble.app.ui.screen.inspection.accelerometer
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Column
|
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.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
|
@ -15,16 +15,18 @@ import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import llc.arma.ble.app.ui.common.rememberBottomDialogState
|
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.AccelerometerMeasure
|
||||||
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.DisplayState
|
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.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
|
import llc.arma.ble.domain.model.Ble
|
||||||
|
|
||||||
enum class SheetPage {
|
enum class SheetPage {
|
||||||
MEASURE_HISTORY
|
MEASURE, POWER, WRITE, HISTORY
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AccelerometerScreen(
|
fun AccelerometerScreen(
|
||||||
ble: Ble.Accelerometer,
|
ble: Ble.Accelerometer,
|
||||||
|
|
@ -43,9 +45,11 @@ fun AccelerometerScreen(
|
||||||
mutableStateOf<SheetPage?>(null)
|
mutableStateOf<SheetPage?>(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
LaunchedEffect(sheetPage) {
|
LaunchedEffect(sheetPage) {
|
||||||
when (sheetPage) {
|
when (sheetPage) {
|
||||||
SheetPage.MEASURE_HISTORY -> launch {
|
SheetPage.MEASURE -> launch {
|
||||||
val currentState = viewModel.viewState.value
|
val currentState = viewModel.viewState.value
|
||||||
|
|
||||||
if (currentState is AccelerometerContract.State.Display) {
|
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 -> {
|
null -> {
|
||||||
bottomDialog.hide()
|
bottomDialog.hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DisposableEffect(key1 = Unit, effect = {
|
||||||
|
onDispose {
|
||||||
|
scope.launch {
|
||||||
|
bottomDialog.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
LaunchedEffect("effect"){
|
LaunchedEffect("effect"){
|
||||||
viewModel.effect.onEach {
|
viewModel.effect.onEach {
|
||||||
when(it){
|
when(it){
|
||||||
AccelerometerContract.Effect.ShowAccelerometerMeasure -> {
|
AccelerometerContract.Effect.ShowAccelerometerMeasure -> {
|
||||||
sheetPage = null
|
sheetPage = null
|
||||||
delay(100)
|
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)
|
}.launchIn(this)
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,22 @@
|
||||||
package llc.arma.ble.app.ui.screen.inspection.accelerometer
|
package llc.arma.ble.app.ui.screen.inspection.accelerometer
|
||||||
|
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
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.BaseViewModel
|
||||||
import llc.arma.ble.app.ui.mapper.BleMapper
|
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.model.BleView
|
||||||
import llc.arma.ble.app.ui.screen.inspection.thermometer.ThermometerContract
|
import llc.arma.ble.app.ui.screen.inspection.thermometer.ThermometerContract
|
||||||
import llc.arma.ble.domain.model.Ble
|
import llc.arma.ble.domain.model.Ble
|
||||||
|
import llc.arma.ble.domain.usecase.WriteBle
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class AccelerometerViewModel @Inject constructor(
|
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>() {
|
) : BaseViewModel<AccelerometerContract.State, AccelerometerContract.Event, AccelerometerContract.Effect>() {
|
||||||
|
|
||||||
override fun setInitialState() = AccelerometerContract.State.Loading
|
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.OnBleChanged -> reduce(viewState.value, event)
|
||||||
is AccelerometerContract.Event.OnHideAccelerometerMeasure -> reduce(viewState.value, event)
|
is AccelerometerContract.Event.OnHideAccelerometerMeasure -> reduce(viewState.value, event)
|
||||||
is AccelerometerContract.Event.OnShowAccelerometerMeasure -> 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 {
|
is AccelerometerContract.State.Display -> setState {
|
||||||
state.copy(
|
state.copy(
|
||||||
origin = Ble.Accelerometer(
|
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 {
|
is AccelerometerContract.State.Loading -> setState {
|
||||||
AccelerometerContract.State.Display(
|
AccelerometerContract.State.Display(
|
||||||
origin = event.ble,
|
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
|
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
|
|
@ -22,10 +23,13 @@ import llc.arma.ble.domain.model.BleInfo
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.Refresh
|
import androidx.compose.material.icons.rounded.Refresh
|
||||||
|
import androidx.compose.ui.graphics.StrokeCap
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import com.patrykandpatrick.vico.compose.axis.horizontal.bottomAxis
|
import com.patrykandpatrick.vico.compose.axis.horizontal.bottomAxis
|
||||||
import com.patrykandpatrick.vico.compose.chart.column.columnChart
|
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.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.chart.scale.AutoScaleUp
|
||||||
import com.patrykandpatrick.vico.core.entry.FloatEntry
|
import com.patrykandpatrick.vico.core.entry.FloatEntry
|
||||||
import com.patrykandpatrick.vico.core.scroll.AutoScrollCondition
|
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.Job
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import llc.arma.ble.domain.usecase.Accelereate
|
||||||
import llc.arma.ble.domain.usecase.GetAccelerometerMeasureBySerialFlow
|
import llc.arma.ble.domain.usecase.GetAccelerometerMeasureBySerialFlow
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -40,17 +45,21 @@ fun AccelerometerMeasure(
|
||||||
ble: BleInfo
|
ble: BleInfo
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val viewModel = hiltViewModel<AccelerometerHistoryViewModel>()
|
val viewModel = hiltViewModel<AccelerometerMeasureViewModel>()
|
||||||
val state = viewModel.viewState.value
|
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))
|
||||||
}
|
}*/
|
||||||
|
|
||||||
|
viewModel.setEvent(AccelerometerMeasureContract.Event.OnStart(ble.serial))
|
||||||
|
|
||||||
|
DisposableEffect(key1 = ble, effect = {
|
||||||
|
|
||||||
DisposableEffect(key1 = ble.serial, effect = {
|
|
||||||
onDispose {
|
onDispose {
|
||||||
viewModel.setEvent(AccelerometerMeasureContract.Event.StopMeasure)
|
viewModel.setEvent(AccelerometerMeasureContract.Event.StopMeasure)
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
|
|
@ -118,37 +127,104 @@ fun Display(
|
||||||
|
|
||||||
if (state.measureHistory.isEmpty()) {
|
if (state.measureHistory.isEmpty()) {
|
||||||
|
|
||||||
Text(
|
CircularProgressIndicator(
|
||||||
modifier = Modifier.align(Alignment.Center),
|
modifier = Modifier.align(Alignment.Center),
|
||||||
text = "Нет данных"
|
strokeCap = StrokeCap.Round
|
||||||
)
|
)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
val producer = remember {
|
val xProducer = remember {
|
||||||
ChartEntryModelProducer(listOf<FloatEntry>())
|
ChartEntryModelProducer(listOf<FloatEntry>())
|
||||||
}
|
}
|
||||||
|
|
||||||
producer.setEntries(state.measureHistory.mapIndexed { index, measurePoint ->
|
val yProducer = remember {
|
||||||
FloatEntry(index.toFloat(), measurePoint)
|
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(
|
zProducer.setEntries(state.measureHistory.mapIndexed { index, measurePoint ->
|
||||||
chart = lineChart,
|
FloatEntry(index.toFloat(), measurePoint.z)
|
||||||
chartModelProducer = producer,
|
})
|
||||||
startAxis = startAxis(),
|
|
||||||
bottomAxis = bottomAxis(),
|
val lineChart = lineChart(
|
||||||
modifier = Modifier.fillMaxSize(),
|
decorations = listOf(
|
||||||
autoScaleUp = AutoScaleUp.None,
|
ThresholdLine(
|
||||||
diffAnimationSpec = tween(0),
|
thresholdValue = 0f
|
||||||
chartScrollSpec = rememberChartScrollSpec(
|
)
|
||||||
initialScroll = InitialScroll.End,
|
|
||||||
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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
|
@Composable
|
||||||
fun Exception(
|
private fun Exception(
|
||||||
|
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
|
|
@ -195,7 +271,7 @@ class AccelerometerMeasureContract {
|
||||||
sealed class State : ViewState {
|
sealed class State : ViewState {
|
||||||
|
|
||||||
data class Display(
|
data class Display(
|
||||||
val measureHistory : List<Float>
|
val measureHistory : List<Accelereate>
|
||||||
) : State()
|
) : State()
|
||||||
|
|
||||||
object Exception : State()
|
object Exception : State()
|
||||||
|
|
@ -211,7 +287,7 @@ class AccelerometerMeasureContract {
|
||||||
|
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class AccelerometerHistoryViewModel @Inject constructor(
|
class AccelerometerMeasureViewModel @Inject constructor(
|
||||||
private val getAccelerometerMeasureBySerialFlow: GetAccelerometerMeasureBySerialFlow,
|
private val getAccelerometerMeasureBySerialFlow: GetAccelerometerMeasureBySerialFlow,
|
||||||
) : BaseViewModel<AccelerometerMeasureContract.State, AccelerometerMeasureContract.Event, AccelerometerMeasureContract.Effect>() {
|
) : BaseViewModel<AccelerometerMeasureContract.State, AccelerometerMeasureContract.Event, AccelerometerMeasureContract.Effect>() {
|
||||||
|
|
||||||
|
|
@ -237,6 +313,8 @@ class AccelerometerHistoryViewModel @Inject constructor(
|
||||||
) {
|
) {
|
||||||
|
|
||||||
measureJob?.cancel()
|
measureJob?.cancel()
|
||||||
|
measureJob = null
|
||||||
|
|
||||||
setState {
|
setState {
|
||||||
AccelerometerMeasureContract.State.Display(emptyList())
|
AccelerometerMeasureContract.State.Display(emptyList())
|
||||||
}
|
}
|
||||||
|
|
@ -262,6 +340,8 @@ class AccelerometerHistoryViewModel @Inject constructor(
|
||||||
private fun startReadMeasure(serial: String, restartJob: Boolean){
|
private fun startReadMeasure(serial: String, restartJob: Boolean){
|
||||||
|
|
||||||
if(restartJob || measureJob == null) {
|
if(restartJob || measureJob == null) {
|
||||||
|
measureJob?.cancel()
|
||||||
|
measureJob = null
|
||||||
measureJob = viewModelScope.launch {
|
measureJob = viewModelScope.launch {
|
||||||
|
|
||||||
setState {
|
setState {
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,47 @@ fun DisplayState(
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
content = {
|
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(
|
Box(
|
||||||
modifier = Modifier.padding(
|
modifier = Modifier.padding(
|
||||||
vertical = 8.dp,
|
vertical = 8.dp,
|
||||||
|
|
@ -72,7 +113,43 @@ fun DisplayState(
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Text(
|
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,
|
shape = CircleShape,
|
||||||
color = MaterialTheme.colorScheme.primaryContainer,
|
color = MaterialTheme.colorScheme.primaryContainer,
|
||||||
onClick = {
|
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 }
|
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
|
@Composable
|
||||||
fun ThermometerScreen(
|
fun ThermometerScreen(
|
||||||
ble: Ble.Thermometer,
|
ble: Ble.Thermometer,
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ fun PowerEdit(
|
||||||
onClick = { value = it }
|
onClick = { value = it }
|
||||||
)
|
)
|
||||||
|
|
||||||
Text(text = it.value.toString() + " db")
|
Text(text = it.value.toString() + " dBb (${it.powerPercentage} %)")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -221,7 +221,7 @@ fun Display(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Exception(
|
private fun Exception(
|
||||||
|
|
||||||
) {
|
) {
|
||||||
Box(
|
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.BleInfo
|
||||||
import llc.arma.ble.domain.model.ConnectedBleInfo
|
import llc.arma.ble.domain.model.ConnectedBleInfo
|
||||||
import llc.arma.ble.domain.repository.BleRepository
|
import llc.arma.ble.domain.repository.BleRepository
|
||||||
|
import llc.arma.ble.domain.usecase.Accelereate
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
@ -32,9 +33,9 @@ import kotlin.random.Random
|
||||||
|
|
||||||
val serviceUUID: UUID = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002")
|
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 accelerometerReadUUID: UUID = UUID.fromString("00002713-0000-1000-8000-00805f9b34fb")
|
||||||
val temperatureHistoryReadUUID: UUID = UUID.fromString("a77db2d8-9bc4-11ed-a8fc-0242ac120002")
|
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 temperatureReadUUID: UUID = UUID.fromString("00002a6e-0000-1000-8000-00805f9b34fb")
|
||||||
val intervalReadUUID: UUID = UUID.fromString("a77db2d8-9bc4-11ed-a8fc-0242ac120002")
|
val intervalReadUUID: UUID = UUID.fromString("a77db2d8-9bc4-11ed-a8fc-0242ac120002")
|
||||||
val intervalWriteUUID: UUID = UUID.fromString("a77db6f2-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,
|
batteryLevel = batteryLevel ?: 0,
|
||||||
rssi = rssi,
|
rssi = rssi,
|
||||||
type = type,
|
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 {
|
} else {
|
||||||
newResult.rssi
|
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(
|
override suspend fun getTemperatureHistoryBySerial(
|
||||||
serial: String
|
serial: String
|
||||||
): Flow<Result<ProgressState<List<Ble.Thermometer.MeasurePoint>>, BleException>> {
|
): 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(
|
override suspend fun changeBlePassword(
|
||||||
password: String,
|
password: String,
|
||||||
serial: String
|
serial: String
|
||||||
|
|
@ -618,53 +706,28 @@ class BleRepositoryImpl @Inject constructor(
|
||||||
return Result.success(Unit)
|
return Result.success(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getAccelerometerMeasureBySerialFlow(serial: String): Flow<Result<Float, BleException>> {
|
override fun getAccelerometerMeasureBySerialFlow(serial: String): Flow<Result<Accelereate, BleException>> {
|
||||||
|
|
||||||
return callbackFlow {
|
return callbackFlow {
|
||||||
|
|
||||||
|
var gatt: BluetoothGatt? = null
|
||||||
|
|
||||||
deviceCache[serial]?.let {
|
deviceCache[serial]?.let {
|
||||||
|
|
||||||
it.device.connectGatt(app, false, object : BluetoothGattCallback() {
|
gatt = it.device.connectGatt(
|
||||||
|
app,
|
||||||
override fun onConnectionStateChange(
|
false,
|
||||||
gatt: BluetoothGatt,
|
ReadAccelerometerCallback(app){ result ->
|
||||||
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())
|
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
send(result)
|
||||||
send(
|
|
||||||
Result.success(((-256)..256).random().toFloat())
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
})
|
|
||||||
|
|
||||||
}
|
}
|
||||||
awaitClose {
|
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)
|
super.onConnectionStateChange(gatt, status, newState)
|
||||||
|
|
||||||
Log.d("beacon", "onConnectionStateChange $status $newState")
|
|
||||||
|
|
||||||
if(checkPermission()) {
|
if(checkPermission()) {
|
||||||
|
|
||||||
if(status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
|
if(status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
|
||||||
|
|
@ -56,7 +54,6 @@ class WriteBeaconCallback(
|
||||||
gatt: BluetoothGatt,
|
gatt: BluetoothGatt,
|
||||||
status: Int
|
status: Int
|
||||||
) {
|
) {
|
||||||
Log.d("beacon", "onServicesDiscovered $status")
|
|
||||||
super.onServicesDiscovered(gatt, status)
|
super.onServicesDiscovered(gatt, status)
|
||||||
onCycle(gatt, status)
|
onCycle(gatt, status)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,18 @@ sealed class Ble(
|
||||||
) {
|
) {
|
||||||
|
|
||||||
class Accelerometer(
|
class Accelerometer(
|
||||||
info: BleInfo
|
info: BleInfo,
|
||||||
): Ble(info){
|
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 batteryLevel: Int,
|
||||||
val rssi: Int?,
|
val rssi: Int?,
|
||||||
val type: Type,
|
val type: Type,
|
||||||
val scanTime: Long
|
val scanTime: Long,
|
||||||
|
val tx: Int
|
||||||
){
|
){
|
||||||
|
|
||||||
enum class Type {
|
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.Ble
|
||||||
import llc.arma.ble.domain.model.BleInfo
|
import llc.arma.ble.domain.model.BleInfo
|
||||||
import llc.arma.ble.domain.model.ConnectedBleInfo
|
import llc.arma.ble.domain.model.ConnectedBleInfo
|
||||||
|
import llc.arma.ble.domain.usecase.Accelereate
|
||||||
import llc.arma.ble.domain.usecase.GetBleBySerial
|
import llc.arma.ble.domain.usecase.GetBleBySerial
|
||||||
|
|
||||||
interface BleRepository {
|
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.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>
|
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
|
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)
|
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)
|
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