From 4528a7cd9ad0e1ab0897553b70e307d937cc38a3 Mon Sep 17 00:00:00 2001 From: Vineyro Date: Tue, 18 Jul 2023 15:02:11 +0700 Subject: [PATCH] Add accelerometer history --- .idea/kotlinc.xml | 6 + app/build.gradle | 4 +- .../java/llc/arma/ble/app/ui/MainActivity.kt | 4 + .../llc/arma/ble/app/ui/common/SignalLevel.kt | 50 +++ .../llc/arma/ble/app/ui/mapper/BleMapper.kt | 3 + .../arma/ble/app/ui/mapper/BleViewMapper.kt | 5 +- .../java/llc/arma/ble/app/ui/model/BleView.kt | 23 +- .../ble/app/ui/screen/ble/BleListScreen.kt | 56 ++- .../accelerometer/AccelerometerContract.kt | 47 ++- .../accelerometer/AccelerometerScreen.kt | 90 ++++- .../accelerometer/AccelerometerViewModel.kt | 181 ++++++++- .../accelerometer/view/AcceleromterHistory.kt | 358 ++++++++++++++++++ .../accelerometer/view/AcceleromterMeasure.kt | 128 +++++-- .../accelerometer/view/DisplayState.kt | 81 +++- .../accelerometer/view/PowerEdit.kt | 97 +++++ .../inspection/accelerometer/view/Write.kt | 349 +++++++++++++++++ .../inspection/beacon/view/PowerEdit.kt | 2 +- .../thermometer/ThermometerScreen.kt | 1 - .../inspection/thermometer/view/PowerEdit.kt | 2 +- .../thermometer/view/TemperatureHistory.kt | 2 +- .../llc/arma/ble/data/BleRepositoryImpl.kt | 141 +++++-- .../ble/data/ReadAccelerometerCallback.kt | 153 ++++++++ .../data/ReadAcceleromterHistoryCallback.kt | 342 +++++++++++++++++ .../ble/data/WriteAccelerometerCallback.kt | 209 ++++++++++ .../llc/arma/ble/data/WriteBeaconCallback.kt | 3 - .../java/llc/arma/ble/domain/model/Ble.kt | 14 +- .../java/llc/arma/ble/domain/model/BleInfo.kt | 3 +- .../ble/domain/repository/BleRepository.kt | 8 +- .../GetAccelerometerHistoryBySerial.kt | 21 + .../GetAccelerometerMeasureBySerialFlow.kt | 10 +- .../llc/arma/ble/domain/usecase/WriteBle.kt | 7 + 31 files changed, 2302 insertions(+), 98 deletions(-) create mode 100644 .idea/kotlinc.xml create mode 100644 app/src/main/java/llc/arma/ble/app/ui/common/SignalLevel.kt create mode 100644 app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/view/AcceleromterHistory.kt create mode 100644 app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/view/PowerEdit.kt create mode 100644 app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/view/Write.kt create mode 100644 app/src/main/java/llc/arma/ble/data/ReadAccelerometerCallback.kt create mode 100644 app/src/main/java/llc/arma/ble/data/ReadAcceleromterHistoryCallback.kt create mode 100644 app/src/main/java/llc/arma/ble/data/WriteAccelerometerCallback.kt create mode 100644 app/src/main/java/llc/arma/ble/domain/usecase/GetAccelerometerHistoryBySerial.kt diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml new file mode 100644 index 0000000..0fc3113 --- /dev/null +++ b/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 9312e0e..5b870cc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { applicationId "llc.arma.ble" minSdk 24 targetSdk 33 - versionCode 1 - versionName "1.0" + versionCode 2 + versionName "1.1" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/src/main/java/llc/arma/ble/app/ui/MainActivity.kt b/app/src/main/java/llc/arma/ble/app/ui/MainActivity.kt index c2969e7..3550b9d 100644 --- a/app/src/main/java/llc/arma/ble/app/ui/MainActivity.kt +++ b/app/src/main/java/llc/arma/ble/app/ui/MainActivity.kt @@ -56,6 +56,10 @@ class MainActivity : ComponentActivity() { mutableStateOf<@Composable () -> Unit>({}) } + if(modalState.currentValue == ModalBottomSheetValue.Hidden){ + sheetContent = {} + } + CompositionLocalProvider( LocalBottomDialogState provides BottomState( sheetState = modalState, diff --git a/app/src/main/java/llc/arma/ble/app/ui/common/SignalLevel.kt b/app/src/main/java/llc/arma/ble/app/ui/common/SignalLevel.kt new file mode 100644 index 0000000..3bcede3 --- /dev/null +++ b/app/src/main/java/llc/arma/ble/app/ui/common/SignalLevel.kt @@ -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) + ) { } + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/app/ui/mapper/BleMapper.kt b/app/src/main/java/llc/arma/ble/app/ui/mapper/BleMapper.kt index c0296c5..bd135b5 100644 --- a/app/src/main/java/llc/arma/ble/app/ui/mapper/BleMapper.kt +++ b/app/src/main/java/llc/arma/ble/app/ui/mapper/BleMapper.kt @@ -35,6 +35,9 @@ class BleMapper @Inject constructor( is Ble.Accelerometer -> { BleView.Accelerometer( info = input.info, + state = BleView.BleState( + tx = txMapper.map(input.state.tx) + ), ) } } diff --git a/app/src/main/java/llc/arma/ble/app/ui/mapper/BleViewMapper.kt b/app/src/main/java/llc/arma/ble/app/ui/mapper/BleViewMapper.kt index c8717ad..4f3229d 100644 --- a/app/src/main/java/llc/arma/ble/app/ui/mapper/BleViewMapper.kt +++ b/app/src/main/java/llc/arma/ble/app/ui/mapper/BleViewMapper.kt @@ -34,7 +34,10 @@ class BleViewMapper @Inject constructor( is BleView.Accelerometer -> { Ble.Accelerometer( - info = input.info + info = input.info, + state = Ble.BleState( + tx = txMapper.map(input.state.tx) + ), ) } } diff --git a/app/src/main/java/llc/arma/ble/app/ui/model/BleView.kt b/app/src/main/java/llc/arma/ble/app/ui/model/BleView.kt index eb72931..a77cc30 100644 --- a/app/src/main/java/llc/arma/ble/app/ui/model/BleView.kt +++ b/app/src/main/java/llc/arma/ble/app/ui/model/BleView.kt @@ -10,7 +10,8 @@ sealed class BleView( ) { class Accelerometer( - info: BleInfo + info: BleInfo, + val state: BleState ) : BleView(info) class Beacon( @@ -58,9 +59,27 @@ sealed class BleView( MINUS_4(-4), ZERO(0), PLUS_3(3), - PLUS_4(4) + PLUS_4(4); + + val powerPercentage: Int + get() { + return when(this){ + MINUS_40 -> 1 + MINUS_20 -> 5 + MINUS_16 -> 7 + MINUS_12 -> 10 + MINUS_8 -> 16 + MINUS_4 -> 20 + ZERO -> 40 + PLUS_3 -> 80 + PLUS_4 -> 100 + } + } + } + + } } \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/ble/BleListScreen.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/ble/BleListScreen.kt index 1ea723d..2faaba7 100644 --- a/app/src/main/java/llc/arma/ble/app/ui/screen/ble/BleListScreen.kt +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/ble/BleListScreen.kt @@ -15,6 +15,7 @@ import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -29,9 +30,11 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import llc.arma.ble.app.ui.common.SignalLevel import llc.arma.ble.app.ui.common.rememberBottomDialogState import llc.arma.ble.domain.model.BleInfo import llc.arma.ble.domain.model.ConnectedBleInfo +import kotlin.math.pow @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -184,6 +187,16 @@ private fun ItemIcon( } +private fun Int.toSignalLevel(): Int { + return when(this){ + in -30 downTo -52 -> 4 + in -51 downTo -63 -> 3 + in -62 downTo -75 -> 2 + in -74 downTo -89 -> 1 + else -> 0 + } +} + @Composable private fun BleItem( ble: BleInfo, @@ -254,6 +267,41 @@ private fun BleItem( text = ble.serial ) + /*Text( + style = MaterialTheme.typography.bodyMedium, + text = String.format("%.3f", (10.0.pow((ble.tx.toDouble() - (ble.rssi?.toDouble() ?: 0.0) - 74) / 20))) + ) + + Text( + style = MaterialTheme.typography.bodyMedium, + text = String.format("%.3f", (ble.tx.toDouble() - (ble.rssi?.toDouble() ?: 0.0))) + ) + + Text( + style = MaterialTheme.typography.bodyMedium, + text = ble.tx.toString() + " tx" + )*/ + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.alpha(0.7f) + ) { + + Icon( + modifier = Modifier.size(16.dp), + imageVector = Icons.Rounded.CompareArrows, + contentDescription = null + ) + + Spacer(modifier = Modifier.width(4.dp)) + + Text( + style = MaterialTheme.typography.bodyMedium, + text = String.format("%.3f", (10.0.pow((ble.tx.toDouble() - (ble.rssi?.toDouble() ?: 0.0) - 74) / 20))) + " м." + ) + + } + Row( horizontalArrangement = Arrangement.spacedBy(8.dp) ) { @@ -263,12 +311,10 @@ private fun BleItem( modifier = Modifier.alpha(0.7f) ) { - Icon( - modifier = Modifier.size(16.dp), - imageVector = Icons.Rounded.NetworkCell, - contentDescription = null - ) + SignalLevel(level = ble.rssi?.toSignalLevel() ?: 0) + Spacer(modifier = Modifier.width(4.dp)) + Box { Text( diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/AccelerometerContract.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/AccelerometerContract.kt index f2df050..1e777d9 100644 --- a/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/AccelerometerContract.kt +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/AccelerometerContract.kt @@ -15,10 +15,26 @@ class AccelerometerContract { object OnHideAccelerometerMeasure : Event() + object OnShowAccelerometerHistory : Event() + + object OnHideAccelerometerHistory : Event() + + object OnPowerEdit : Event() + + object OnShowWriteBlePreview : Event() + + object OnWriteBle : Event() + + object OnHideWriteBlePreview : Event() + data class OnBleChanged( val ble: Ble.Accelerometer, ): Event() + data class OnPowerChanged( + val tx: BleView.BleState.TX + ) : Event() + } sealed class State : ViewState { @@ -28,7 +44,26 @@ class AccelerometerContract { data class Display( val origin: Ble.Accelerometer, val accelerometer: BleView.Accelerometer, - ) : State() + val writeState: WriteState? + ) : State() { + + sealed class WriteState { + + data class DisplayPreview( + val writeRequest: Ble.Accelerometer.WriteRequest + ) : WriteState() + + data class Writing( + val writeRequest: Ble.Accelerometer.WriteRequest + ) : WriteState() + + object Success : WriteState() + + object Failure : WriteState() + + } + + } } @@ -36,6 +71,16 @@ class AccelerometerContract { object ShowAccelerometerMeasure : Effect() + object ShowAccelerometerHistory : Effect() + + object ShowPowerPicker : Effect() + + object HidePowerPicker : Effect() + + object ShowWriteBle : Effect() + + object HideWriteBle : Effect() + } } \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/AccelerometerScreen.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/AccelerometerScreen.kt index 8b95fc6..69fcde6 100644 --- a/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/AccelerometerScreen.kt +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/AccelerometerScreen.kt @@ -1,12 +1,12 @@ package llc.arma.ble.app.ui.screen.inspection.accelerometer import androidx.compose.foundation.layout.Column -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.ModalBottomSheetValue import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.hilt.navigation.compose.hiltViewModel @@ -15,16 +15,18 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import llc.arma.ble.app.ui.common.rememberBottomDialogState +import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelerometerHistory import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelerometerMeasure import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.DisplayState import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.LoadingState +import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.PowerEdit +import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.Write import llc.arma.ble.domain.model.Ble enum class SheetPage { - MEASURE_HISTORY + MEASURE, POWER, WRITE, HISTORY } -@OptIn(ExperimentalMaterialApi::class) @Composable fun AccelerometerScreen( ble: Ble.Accelerometer, @@ -43,9 +45,11 @@ fun AccelerometerScreen( mutableStateOf(null) } + val scope = rememberCoroutineScope() + LaunchedEffect(sheetPage) { when (sheetPage) { - SheetPage.MEASURE_HISTORY -> launch { + SheetPage.MEASURE -> launch { val currentState = viewModel.viewState.value if (currentState is AccelerometerContract.State.Display) { @@ -54,19 +58,93 @@ fun AccelerometerScreen( } } } + SheetPage.HISTORY -> launch { + val currentState = viewModel.viewState.value + + if (currentState is AccelerometerContract.State.Display) { + bottomDialog.show { + AccelerometerHistory(ble = currentState.accelerometer.info) + } + } + } + SheetPage.POWER -> bottomDialog.show { + + val currentState = viewModel.viewState.value + + if(currentState is AccelerometerContract.State.Display) { + PowerEdit( + state = currentState.accelerometer, + onEvent = { + viewModel.setEvent(it) + } + ) + } + + } + SheetPage.WRITE -> bottomDialog.show { + + val currentState = viewModel.viewState.value + + if (currentState is AccelerometerContract.State.Display) { + + currentState.writeState?.let { + + Write( + state = it, + onEvent = { + viewModel.setEvent(it) + } + ) + + } + + } + } null -> { bottomDialog.hide() } } } + DisposableEffect(key1 = Unit, effect = { + onDispose { + scope.launch { + bottomDialog.hide() + } + } + }) + LaunchedEffect("effect"){ viewModel.effect.onEach { when(it){ AccelerometerContract.Effect.ShowAccelerometerMeasure -> { sheetPage = null delay(100) - sheetPage = SheetPage.MEASURE_HISTORY + sheetPage = SheetPage.MEASURE + } + is AccelerometerContract.Effect.HidePowerPicker -> launch { + sheetPage = null + delay(100) + } + is AccelerometerContract.Effect.ShowPowerPicker -> launch { + sheetPage = null + delay(100) + sheetPage = SheetPage.POWER + } + is AccelerometerContract.Effect.HideWriteBle -> { + sheetPage = null + delay(100) + } + is AccelerometerContract.Effect.ShowWriteBle -> { + sheetPage = null + delay(100) + sheetPage = SheetPage.WRITE + } + + AccelerometerContract.Effect.ShowAccelerometerHistory -> { + sheetPage = null + delay(100) + sheetPage = SheetPage.HISTORY } } }.launchIn(this) diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/AccelerometerViewModel.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/AccelerometerViewModel.kt index 34b09a9..8595d78 100644 --- a/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/AccelerometerViewModel.kt +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/AccelerometerViewModel.kt @@ -1,16 +1,22 @@ package llc.arma.ble.app.ui.screen.inspection.accelerometer +import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch import llc.arma.ble.app.ui.common.BaseViewModel import llc.arma.ble.app.ui.mapper.BleMapper +import llc.arma.ble.app.ui.mapper.BleViewMapper import llc.arma.ble.app.ui.model.BleView import llc.arma.ble.app.ui.screen.inspection.thermometer.ThermometerContract import llc.arma.ble.domain.model.Ble +import llc.arma.ble.domain.usecase.WriteBle import javax.inject.Inject @HiltViewModel class AccelerometerViewModel @Inject constructor( - private val bleMapper: BleMapper + private val bleMapper: BleMapper, + private val bleViewMapper: BleViewMapper, + private val writeBle: WriteBle ) : BaseViewModel() { override fun setInitialState() = AccelerometerContract.State.Loading @@ -20,6 +26,109 @@ class AccelerometerViewModel @Inject constructor( is AccelerometerContract.Event.OnBleChanged -> reduce(viewState.value, event) is AccelerometerContract.Event.OnHideAccelerometerMeasure -> reduce(viewState.value, event) is AccelerometerContract.Event.OnShowAccelerometerMeasure -> reduce(viewState.value, event) + is AccelerometerContract.Event.OnPowerChanged -> reduce(viewState.value, event) + is AccelerometerContract.Event.OnPowerEdit -> reduce(viewState.value, event) + is AccelerometerContract.Event.OnShowWriteBlePreview -> reduce(viewState.value, event) + is AccelerometerContract.Event.OnHideWriteBlePreview -> reduce(viewState.value, event) + is AccelerometerContract.Event.OnWriteBle -> reduce(viewState.value, event) + is AccelerometerContract.Event.OnHideAccelerometerHistory -> reduce(viewState.value, event) + is AccelerometerContract.Event.OnShowAccelerometerHistory -> reduce(viewState.value, event) + } + } + + private fun reduce( + state: AccelerometerContract.State, + event: AccelerometerContract.Event.OnShowAccelerometerHistory + ) { + + setEffect { + AccelerometerContract.Effect.ShowAccelerometerHistory + } + + } + + private fun reduce( + state: AccelerometerContract.State, + event: AccelerometerContract.Event.OnHideAccelerometerHistory + ) { + + + + } + + private fun reduce( + state: AccelerometerContract.State, + event: AccelerometerContract.Event.OnHideWriteBlePreview + ) { + + if(state is AccelerometerContract.State.Display){ + + setState { + state.copy( + writeState = null + ) + } + + } + + setEffect { + AccelerometerContract.Effect.HideWriteBle + } + + } + + private fun reduce( + state: AccelerometerContract.State, + event: AccelerometerContract.Event.OnShowWriteBlePreview + ) { + + if(state is AccelerometerContract.State.Display){ + + val newBle = bleViewMapper.map(state.accelerometer) as Ble.Accelerometer + + val writeRequest = Ble.Accelerometer.WriteRequest( + tx = if(newBle.state.tx == state.origin.state.tx) null else newBle.state.tx + ) + + setState { + state.copy( + writeState = AccelerometerContract.State.Display.WriteState.DisplayPreview( + writeRequest + ) + ) + } + + setEffect { + AccelerometerContract.Effect.ShowWriteBle + } + + } + + } + + private fun reduce( + state: AccelerometerContract.State, + event: AccelerometerContract.Event.OnPowerChanged + ) { + + if(state is AccelerometerContract.State.Display) { + + state.accelerometer.state.tx = event.tx + + } + + setEffect { + AccelerometerContract.Effect.HidePowerPicker + } + + } + + private fun reduce( + state: AccelerometerContract.State, + event: AccelerometerContract.Event.OnPowerEdit + ) { + setEffect { + AccelerometerContract.Effect.ShowPowerPicker } } @@ -52,7 +161,8 @@ class AccelerometerViewModel @Inject constructor( is AccelerometerContract.State.Display -> setState { state.copy( origin = Ble.Accelerometer( - info = event.ble.info + info = event.ble.info, + state = event.ble.state ) ) } @@ -60,11 +170,76 @@ class AccelerometerViewModel @Inject constructor( is AccelerometerContract.State.Loading -> setState { AccelerometerContract.State.Display( origin = event.ble, - accelerometer = bleMapper.map(event.ble) as BleView.Accelerometer + accelerometer = bleMapper.map(event.ble) as BleView.Accelerometer, + writeState = null ) } } } + private fun reduce( + state: AccelerometerContract.State, + event: AccelerometerContract.Event.OnWriteBle + ) { + + if(state is AccelerometerContract.State.Display){ + + state.writeState?.let { request -> + + if(request is AccelerometerContract.State.Display.WriteState.DisplayPreview) { + + viewModelScope.launch { + + setState { + state.copy( + writeState = AccelerometerContract.State.Display.WriteState.Writing( + request.writeRequest + ) + ) + } + + writeBle(state.accelerometer.info.serial, request.writeRequest).fold( + onSuccess = { + + val currentState = viewState.value + + if(currentState is AccelerometerContract.State.Display) { + + val newBleObject = Ble.Accelerometer( + info = currentState.origin.info, + state = currentState.origin.state.copy( + tx = request.writeRequest.tx ?: state.origin.state.tx + ) + ) + + setState { + currentState.copy( + origin = newBleObject, + writeState = AccelerometerContract.State.Display.WriteState.Success + ) + } + + } + + }, + onFailure = { + setState { + state.copy( + writeState = AccelerometerContract.State.Display.WriteState.Failure + ) + } + } + ) + + } + + } + + } + + } + + } + } \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/view/AcceleromterHistory.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/view/AcceleromterHistory.kt new file mode 100644 index 0000000..29644f7 --- /dev/null +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/view/AcceleromterHistory.kt @@ -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() + 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 { 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> + ) : State() + + object Exception : State() + + } + + sealed class Effect : ViewSideEffect { + + } + +} + + + +@HiltViewModel +class AccelerometerHistoryViewModel @Inject constructor( + private val getAccelerometerHistoryBySerial: GetAccelerometerHistoryBySerial +) : BaseViewModel() { + + 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) + + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/view/AcceleromterMeasure.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/view/AcceleromterMeasure.kt index 442d900..215bda4 100644 --- a/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/view/AcceleromterMeasure.kt +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/view/AcceleromterMeasure.kt @@ -1,5 +1,6 @@ package llc.arma.ble.app.ui.screen.inspection.accelerometer.view +import android.util.Log import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.* import androidx.compose.material3.* @@ -22,10 +23,13 @@ import llc.arma.ble.domain.model.BleInfo import javax.inject.Inject import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Refresh +import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.text.style.TextAlign import com.patrykandpatrick.vico.compose.axis.horizontal.bottomAxis import com.patrykandpatrick.vico.compose.chart.column.columnChart +import com.patrykandpatrick.vico.compose.chart.line.lineChart import com.patrykandpatrick.vico.compose.chart.scroll.rememberChartScrollSpec +import com.patrykandpatrick.vico.core.chart.decoration.ThresholdLine import com.patrykandpatrick.vico.core.chart.scale.AutoScaleUp import com.patrykandpatrick.vico.core.entry.FloatEntry import com.patrykandpatrick.vico.core.scroll.AutoScrollCondition @@ -33,6 +37,7 @@ import com.patrykandpatrick.vico.core.scroll.InitialScroll import kotlinx.coroutines.Job import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import llc.arma.ble.domain.usecase.Accelereate import llc.arma.ble.domain.usecase.GetAccelerometerMeasureBySerialFlow @Composable @@ -40,17 +45,21 @@ fun AccelerometerMeasure( ble: BleInfo ) { - val viewModel = hiltViewModel() + val viewModel = hiltViewModel() val state = viewModel.viewState.value - LaunchedEffect(ble.serial) { + /*LaunchedEffect(ble.serial) { viewModel.setEvent(AccelerometerMeasureContract.Event.OnStart(ble.serial)) - } + }*/ + + viewModel.setEvent(AccelerometerMeasureContract.Event.OnStart(ble.serial)) + + DisposableEffect(key1 = ble, effect = { - DisposableEffect(key1 = ble.serial, effect = { onDispose { viewModel.setEvent(AccelerometerMeasureContract.Event.StopMeasure) } + }) Column( @@ -118,37 +127,104 @@ fun Display( if (state.measureHistory.isEmpty()) { - Text( + CircularProgressIndicator( modifier = Modifier.align(Alignment.Center), - text = "Нет данных" + strokeCap = StrokeCap.Round ) } else { - val producer = remember { + val xProducer = remember { ChartEntryModelProducer(listOf()) } - producer.setEntries(state.measureHistory.mapIndexed { index, measurePoint -> - FloatEntry(index.toFloat(), measurePoint) + val yProducer = remember { + ChartEntryModelProducer(listOf()) + } + + val zProducer = remember { + ChartEntryModelProducer(listOf()) + } + + xProducer.setEntries(state.measureHistory.mapIndexed { index, measurePoint -> + FloatEntry(index.toFloat(), measurePoint.x) }) - val lineChart = columnChart() + yProducer.setEntries(state.measureHistory.mapIndexed { index, measurePoint -> + FloatEntry(index.toFloat(), measurePoint.y) + }) - Chart( - chart = lineChart, - chartModelProducer = producer, - startAxis = startAxis(), - bottomAxis = bottomAxis(), - modifier = Modifier.fillMaxSize(), - autoScaleUp = AutoScaleUp.None, - diffAnimationSpec = tween(0), - chartScrollSpec = rememberChartScrollSpec( - initialScroll = InitialScroll.End, - autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased + zProducer.setEntries(state.measureHistory.mapIndexed { index, measurePoint -> + FloatEntry(index.toFloat(), measurePoint.z) + }) + + val lineChart = lineChart( + decorations = listOf( + ThresholdLine( + thresholdValue = 0f + ) ) ) + Column() { + + Text(text = "Ось X:") + + Chart( + chart = lineChart, + chartModelProducer = xProducer, + startAxis = startAxis(), + bottomAxis = bottomAxis(), + modifier = Modifier + .fillMaxWidth() + .weight(1f), + autoScaleUp = AutoScaleUp.None, + diffAnimationSpec = tween(0), + chartScrollSpec = rememberChartScrollSpec( + initialScroll = InitialScroll.End, + autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased, + autoScrollAnimationSpec = tween(0) + ) + ) + + Text(text = "Ось Y:") + Chart( + chart = lineChart, + chartModelProducer = yProducer, + startAxis = startAxis(), + bottomAxis = bottomAxis(), + modifier = Modifier + .fillMaxWidth() + .weight(1f), + autoScaleUp = AutoScaleUp.None, + diffAnimationSpec = tween(0), + chartScrollSpec = rememberChartScrollSpec( + initialScroll = InitialScroll.End, + autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased, + autoScrollAnimationSpec = tween(0) + ) + ) + + Text(text = "Ось Z:") + Chart( + chart = lineChart, + chartModelProducer = zProducer, + startAxis = startAxis(), + bottomAxis = bottomAxis(), + modifier = Modifier + .fillMaxWidth() + .weight(1f), + autoScaleUp = AutoScaleUp.None, + diffAnimationSpec = tween(0), + chartScrollSpec = rememberChartScrollSpec( + initialScroll = InitialScroll.End, + autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased, + autoScrollAnimationSpec = tween(0) + ) + ) + + } + } @@ -156,7 +232,7 @@ fun Display( } @Composable -fun Exception( +private fun Exception( ) { Box( @@ -195,7 +271,7 @@ class AccelerometerMeasureContract { sealed class State : ViewState { data class Display( - val measureHistory : List + val measureHistory : List ) : State() object Exception : State() @@ -211,7 +287,7 @@ class AccelerometerMeasureContract { @HiltViewModel -class AccelerometerHistoryViewModel @Inject constructor( +class AccelerometerMeasureViewModel @Inject constructor( private val getAccelerometerMeasureBySerialFlow: GetAccelerometerMeasureBySerialFlow, ) : BaseViewModel() { @@ -237,6 +313,8 @@ class AccelerometerHistoryViewModel @Inject constructor( ) { measureJob?.cancel() + measureJob = null + setState { AccelerometerMeasureContract.State.Display(emptyList()) } @@ -262,6 +340,8 @@ class AccelerometerHistoryViewModel @Inject constructor( private fun startReadMeasure(serial: String, restartJob: Boolean){ if(restartJob || measureJob == null) { + measureJob?.cancel() + measureJob = null measureJob = viewModelScope.launch { setState { diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/view/DisplayState.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/view/DisplayState.kt index 1da0541..bbee4ce 100644 --- a/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/view/DisplayState.kt +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/view/DisplayState.kt @@ -50,6 +50,47 @@ fun DisplayState( modifier = Modifier, content = { + Box( + modifier = Modifier.padding( + vertical = 8.dp, + horizontal = 8.dp + ) + ) { + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .clip(RoundedCornerShape(16.dp)) + .clickable { + onEvent(AccelerometerContract.Event.OnPowerEdit) + } + .padding(8.dp) + ) { + + Column( + modifier = Modifier.weight(1f) + ) { + + Text( + text = "Мощность" + ) + Text( + color = MaterialTheme.colorScheme.secondary, + style = MaterialTheme.typography.bodyMedium, + text = "${ble.state.tx.value} db" + ) + + } + + Icon( + imageVector = Icons.Rounded.KeyboardArrowDown, + contentDescription = null + ) + + } + + } + Box( modifier = Modifier.padding( vertical = 8.dp, @@ -72,7 +113,43 @@ fun DisplayState( ) { Text( - text = "График измерений" + text = "График" + ) + + } + + Icon( + imageVector = Icons.Rounded.KeyboardArrowRight, + contentDescription = null + ) + + } + + } + + Box( + modifier = Modifier.padding( + vertical = 8.dp, + horizontal = 8.dp + ) + ) { + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .clip(RoundedCornerShape(16.dp)) + .clickable { + onEvent(AccelerometerContract.Event.OnShowAccelerometerHistory) + } + .padding(8.dp) + ) { + + Column( + modifier = Modifier.weight(1f) + ) { + + Text( + text = "История" ) } @@ -99,7 +176,7 @@ fun DisplayState( shape = CircleShape, color = MaterialTheme.colorScheme.primaryContainer, onClick = { - + onEvent(AccelerometerContract.Event.OnShowWriteBlePreview) } ) { diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/view/PowerEdit.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/view/PowerEdit.kt new file mode 100644 index 0000000..2a7503e --- /dev/null +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/view/PowerEdit.kt @@ -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 = "Применить" + ) + + } + + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/view/Write.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/view/Write.kt new file mode 100644 index 0000000..333e666 --- /dev/null +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/accelerometer/view/Write.kt @@ -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 = "Ок" + ) + + } + + } + + } + + } + + } + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/beacon/view/PowerEdit.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/beacon/view/PowerEdit.kt index 7adf422..c3d21ec 100644 --- a/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/beacon/view/PowerEdit.kt +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/beacon/view/PowerEdit.kt @@ -55,7 +55,7 @@ fun PowerEdit( onClick = { value = it } ) - Text(text = it.value.toString() + " db") + Text(text = it.value.toString() + " dBb (${it.powerPercentage} %)") } diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/thermometer/ThermometerScreen.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/thermometer/ThermometerScreen.kt index 465efe2..da5ccc7 100644 --- a/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/thermometer/ThermometerScreen.kt +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/thermometer/ThermometerScreen.kt @@ -52,7 +52,6 @@ val Ble.BleState.TX.localizedName: String } -@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @Composable fun ThermometerScreen( ble: Ble.Thermometer, diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/thermometer/view/PowerEdit.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/thermometer/view/PowerEdit.kt index cb38f26..ca82507 100644 --- a/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/thermometer/view/PowerEdit.kt +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/thermometer/view/PowerEdit.kt @@ -54,7 +54,7 @@ fun PowerEdit( onClick = { value = it } ) - Text(text = it.value.toString() + " db") + Text(text = it.value.toString() + " dBb (${it.powerPercentage} %)") } diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/thermometer/view/TemperatureHistory.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/thermometer/view/TemperatureHistory.kt index 0fd548a..1cb4737 100644 --- a/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/thermometer/view/TemperatureHistory.kt +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/inspection/thermometer/view/TemperatureHistory.kt @@ -221,7 +221,7 @@ fun Display( } @Composable -fun Exception( +private fun Exception( ) { Box( diff --git a/app/src/main/java/llc/arma/ble/data/BleRepositoryImpl.kt b/app/src/main/java/llc/arma/ble/data/BleRepositoryImpl.kt index 03aff5b..53bc0e5 100644 --- a/app/src/main/java/llc/arma/ble/data/BleRepositoryImpl.kt +++ b/app/src/main/java/llc/arma/ble/data/BleRepositoryImpl.kt @@ -24,6 +24,7 @@ import llc.arma.ble.domain.model.Ble import llc.arma.ble.domain.model.BleInfo import llc.arma.ble.domain.model.ConnectedBleInfo import llc.arma.ble.domain.repository.BleRepository +import llc.arma.ble.domain.usecase.Accelereate import java.util.* import javax.inject.Inject import javax.inject.Singleton @@ -32,9 +33,9 @@ import kotlin.random.Random val serviceUUID: UUID = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002") -val accelerometerDescriptorUUID: UUID = UUID.fromString("a77db6f2-9bc4-11ed-a8fc-0242ac120002") val accelerometerReadUUID: UUID = UUID.fromString("00002713-0000-1000-8000-00805f9b34fb") val temperatureHistoryReadUUID: UUID = UUID.fromString("a77db2d8-9bc4-11ed-a8fc-0242ac120002") +val accelerometerHistoryReadUUID: UUID = UUID.fromString("a77db2d8-9bc4-11ed-a8fc-0242ac120002") val temperatureReadUUID: UUID = UUID.fromString("00002a6e-0000-1000-8000-00805f9b34fb") val intervalReadUUID: UUID = UUID.fromString("a77db2d8-9bc4-11ed-a8fc-0242ac120002") val intervalWriteUUID: UUID = UUID.fromString("a77db6f2-9bc4-11ed-a8fc-0242ac120002") @@ -69,7 +70,8 @@ class BleRepositoryImpl @Inject constructor( batteryLevel = batteryLevel ?: 0, rssi = rssi, type = type, - scanTime = timestampNanos / 1_000_000 + scanTime = timestampNanos / 1_000_000, + tx = scanRecord?.txPowerLevel ?: 0 ) } @@ -250,7 +252,19 @@ class BleRepositoryImpl @Inject constructor( } else { newResult.rssi } - + ), + state = Ble.BleState( + tx = when (result.scanRecord?.txPowerLevel) { + -40 -> Ble.BleState.TX.MINUS_40 + -20 -> Ble.BleState.TX.MINUS_20 + -16 -> Ble.BleState.TX.MINUS_16 + -12 -> Ble.BleState.TX.MINUS_12 + -8 -> Ble.BleState.TX.MINUS_8 + -4 -> Ble.BleState.TX.MINUS_4 + 3 -> Ble.BleState.TX.PLUS_3 + 4 -> Ble.BleState.TX.PLUS_4 + else -> Ble.BleState.TX.ZERO + } ) ) @@ -484,6 +498,44 @@ class BleRepositoryImpl @Inject constructor( } + override suspend fun getAccelerometerHistoryBySerial( + serial: String + ): Flow>, BleException>> { + + var gatt: BluetoothGatt? = null + + return callbackFlow { + + deviceCache[serial]?.device?.let { + + if (checkPermission()) { + + gatt = it.connectGatt(app, false, ReadAccelerometerHistoryCallback(app) { + CoroutineScope(Dispatchers.IO).launch { + send(it) + } + }) + + } else { + + CoroutineScope(Dispatchers.IO).launch { + send(Result.failure(BleException.PermissionDenied)) + } + + return@callbackFlow + + } + + } + + awaitClose { + gatt?.close() + } + + } + + } + override suspend fun getTemperatureHistoryBySerial( serial: String ): Flow>, BleException>> { @@ -594,6 +646,42 @@ class BleRepositoryImpl @Inject constructor( } + override suspend fun writeBle( + serial: String, + request: Ble.Accelerometer.WriteRequest + ): Result = suspendCancellableCoroutine { + + deviceCache[serial]?.let { scanResult -> + + if(checkPermission()) { + + var gatt: BluetoothGatt? = null + + val callback = WriteAccelerometerCallback(app, request) { result -> + + gatt?.close() + + result.onSuccess { + deviceCache.remove(serial) + resultList.remove(serial) + } + + it.resume(result) + + } + + gatt = scanResult.device.connectGatt(app, false, callback) + + } else { + + it.resume(Result.failure(BleException.PermissionDenied)) + + } + + } + + } + override suspend fun changeBlePassword( password: String, serial: String @@ -618,53 +706,28 @@ class BleRepositoryImpl @Inject constructor( return Result.success(Unit) } - override fun getAccelerometerMeasureBySerialFlow(serial: String): Flow> { + override fun getAccelerometerMeasureBySerialFlow(serial: String): Flow> { return callbackFlow { + var gatt: BluetoothGatt? = null + deviceCache[serial]?.let { - it.device.connectGatt(app, false, object : BluetoothGattCallback() { - - override fun onConnectionStateChange( - gatt: BluetoothGatt, - status: Int, - newState: Int - ) { - gatt.discoverServices() - } - - override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { - super.onServicesDiscovered(gatt, status) - gatt.getService(serviceUUID)?.getCharacteristic(accelerometerReadUUID)?.let { - gatt.setCharacteristicNotification(it, true) - gatt.writeDescriptor(it.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")), BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE) - } - } - - override fun onCharacteristicChanged( - gatt: BluetoothGatt, - characteristic: BluetoothGattCharacteristic, - value: ByteArray - ) { - - Log.d("new", value.toString()) - + gatt = it.device.connectGatt( + app, + false, + ReadAccelerometerCallback(app){ result -> CoroutineScope(Dispatchers.IO).launch { - - send( - Result.success(((-256)..256).random().toFloat()) - ) - + send(result) } - } - - }) + ) } awaitClose { - + Log.d("acc", "close") + gatt?.close() } } diff --git a/app/src/main/java/llc/arma/ble/data/ReadAccelerometerCallback.kt b/app/src/main/java/llc/arma/ble/data/ReadAccelerometerCallback.kt new file mode 100644 index 0000000..17d721b --- /dev/null +++ b/app/src/main/java/llc/arma/ble/data/ReadAccelerometerCallback.kt @@ -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) -> 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 + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/data/ReadAcceleromterHistoryCallback.kt b/app/src/main/java/llc/arma/ble/data/ReadAcceleromterHistoryCallback.kt new file mode 100644 index 0000000..28955ec --- /dev/null +++ b/app/src/main/java/llc/arma/ble/data/ReadAcceleromterHistoryCallback.kt @@ -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>, 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 = 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{ + + 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) + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/data/WriteAccelerometerCallback.kt b/app/src/main/java/llc/arma/ble/data/WriteAccelerometerCallback.kt new file mode 100644 index 0000000..e87fbc8 --- /dev/null +++ b/app/src/main/java/llc/arma/ble/data/WriteAccelerometerCallback.kt @@ -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 +) : 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? = 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 { + + 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 + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/data/WriteBeaconCallback.kt b/app/src/main/java/llc/arma/ble/data/WriteBeaconCallback.kt index 91a977a..cae6bcd 100644 --- a/app/src/main/java/llc/arma/ble/data/WriteBeaconCallback.kt +++ b/app/src/main/java/llc/arma/ble/data/WriteBeaconCallback.kt @@ -30,8 +30,6 @@ class WriteBeaconCallback( ) { super.onConnectionStateChange(gatt, status, newState) - Log.d("beacon", "onConnectionStateChange $status $newState") - if(checkPermission()) { if(status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) { @@ -56,7 +54,6 @@ class WriteBeaconCallback( gatt: BluetoothGatt, status: Int ) { - Log.d("beacon", "onServicesDiscovered $status") super.onServicesDiscovered(gatt, status) onCycle(gatt, status) diff --git a/app/src/main/java/llc/arma/ble/domain/model/Ble.kt b/app/src/main/java/llc/arma/ble/domain/model/Ble.kt index e09f848..4542051 100644 --- a/app/src/main/java/llc/arma/ble/domain/model/Ble.kt +++ b/app/src/main/java/llc/arma/ble/domain/model/Ble.kt @@ -5,8 +5,18 @@ sealed class Ble( ) { class Accelerometer( - info: BleInfo - ): Ble(info){ + info: BleInfo, + val state: BleState + ): Ble(info) { + + data class WriteRequest( + val tx: BleState.TX?, + ) + + class MeasurePoint ( + val frequency: Long, + val value: Float + ) } diff --git a/app/src/main/java/llc/arma/ble/domain/model/BleInfo.kt b/app/src/main/java/llc/arma/ble/domain/model/BleInfo.kt index 4043f33..cfa058a 100644 --- a/app/src/main/java/llc/arma/ble/domain/model/BleInfo.kt +++ b/app/src/main/java/llc/arma/ble/domain/model/BleInfo.kt @@ -6,7 +6,8 @@ data class BleInfo( val batteryLevel: Int, val rssi: Int?, val type: Type, - val scanTime: Long + val scanTime: Long, + val tx: Int ){ enum class Type { diff --git a/app/src/main/java/llc/arma/ble/domain/repository/BleRepository.kt b/app/src/main/java/llc/arma/ble/domain/repository/BleRepository.kt index 0be5155..633b1d0 100644 --- a/app/src/main/java/llc/arma/ble/domain/repository/BleRepository.kt +++ b/app/src/main/java/llc/arma/ble/domain/repository/BleRepository.kt @@ -7,6 +7,7 @@ import llc.arma.ble.domain.common.ProgressState import llc.arma.ble.domain.model.Ble import llc.arma.ble.domain.model.BleInfo import llc.arma.ble.domain.model.ConnectedBleInfo +import llc.arma.ble.domain.usecase.Accelereate import llc.arma.ble.domain.usecase.GetBleBySerial interface BleRepository { @@ -23,7 +24,12 @@ interface BleRepository { suspend fun writeBle(serial: String, request: Ble.Beacon.WriteRequest): Result + suspend fun writeBle(serial: String, request: Ble.Accelerometer.WriteRequest): Result + suspend fun changeBlePassword(password: String, serial: String): Result - fun getAccelerometerMeasureBySerialFlow(serial: String): Flow> + + fun getAccelerometerMeasureBySerialFlow(serial: String): Flow> + + suspend fun getAccelerometerHistoryBySerial(serial: String): Flow>, BleException>> } \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/domain/usecase/GetAccelerometerHistoryBySerial.kt b/app/src/main/java/llc/arma/ble/domain/usecase/GetAccelerometerHistoryBySerial.kt new file mode 100644 index 0000000..e7496b2 --- /dev/null +++ b/app/src/main/java/llc/arma/ble/domain/usecase/GetAccelerometerHistoryBySerial.kt @@ -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>, BleException>> { + + return bleRepository.getAccelerometerHistoryBySerial(serial) + + } + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/domain/usecase/GetAccelerometerMeasureBySerialFlow.kt b/app/src/main/java/llc/arma/ble/domain/usecase/GetAccelerometerMeasureBySerialFlow.kt index 6a5bbce..aa643fc 100644 --- a/app/src/main/java/llc/arma/ble/domain/usecase/GetAccelerometerMeasureBySerialFlow.kt +++ b/app/src/main/java/llc/arma/ble/domain/usecase/GetAccelerometerMeasureBySerialFlow.kt @@ -12,10 +12,16 @@ class GetAccelerometerMeasureBySerialFlow @Inject constructor( private val bleRepository: BleRepository ) { - operator fun invoke(serial: String): Flow> { + operator fun invoke(serial: String): Flow> { return bleRepository.getAccelerometerMeasureBySerialFlow(serial) } -} \ No newline at end of file +} + +data class Accelereate( + val x: Float, + val y: Float, + val z: Float, +) \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/domain/usecase/WriteBle.kt b/app/src/main/java/llc/arma/ble/domain/usecase/WriteBle.kt index 1bab00b..1732211 100644 --- a/app/src/main/java/llc/arma/ble/domain/usecase/WriteBle.kt +++ b/app/src/main/java/llc/arma/ble/domain/usecase/WriteBle.kt @@ -24,4 +24,11 @@ class WriteBle @Inject constructor( return bleRepository.writeBle(serial, request) } + suspend operator fun invoke( + serial: String, + request: Ble.Accelerometer.WriteRequest + ): llc.arma.ble.domain.Result{ + return bleRepository.writeBle(serial, request) + } + } \ No newline at end of file