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