История изменений акселерометра

This commit is contained in:
Vineyro 2023-09-07 16:22:40 +07:00
parent df30906056
commit c59c929f35
31 changed files with 1894 additions and 396 deletions

View File

@ -14,7 +14,7 @@ android {
minSdk 24 minSdk 24
targetSdk 33 targetSdk 33
versionCode 5 versionCode 5
versionName "1.2.2" versionName "1.2.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {

View File

@ -38,6 +38,10 @@ class BleMapper @Inject constructor(
state = BleView.BleState( state = BleView.BleState(
tx = txMapper.map(input.state.tx) tx = txMapper.map(input.state.tx)
), ),
accelerometerState = BleView.Accelerometer.AccelerometerState(
saveHistory = input.accelerometerState.saveHistory,
historyInterval = input.accelerometerState.historyInterval,
)
) )
} }
} }

View File

@ -38,6 +38,10 @@ class BleViewMapper @Inject constructor(
state = Ble.BleState( state = Ble.BleState(
tx = txMapper.map(input.state.tx) tx = txMapper.map(input.state.tx)
), ),
accelerometerState = Ble.Accelerometer.AccelerometerState(
saveHistory = input.accelerometerState.saveHistory,
historyInterval = input.accelerometerState.historyInterval,
)
) )
} }
} }

View File

@ -11,8 +11,21 @@ sealed class BleView(
class Accelerometer( class Accelerometer(
info: BleInfo, info: BleInfo,
val state: BleState val state: BleState,
) : BleView(info) val accelerometerState: AccelerometerState
) : BleView(info) {
class AccelerometerState(
saveHistory: Boolean,
historyInterval: Long
) {
var saveHistory by mutableStateOf(saveHistory)
var historyInterval by mutableStateOf(historyInterval)
}
}
class Beacon( class Beacon(
info: BleInfo, info: BleInfo,

View File

@ -260,10 +260,12 @@ private fun BleItem(
) { ) {
Box {
ItemIcon { ItemIcon {
Icon( Icon(
modifier = Modifier.align(Alignment.Center), modifier = Modifier.align(Alignment.Center),
imageVector = when(ble.type){ imageVector = when (ble.type) {
BleInfo.Type.BEACON -> Icons.Rounded.Nfc BleInfo.Type.BEACON -> Icons.Rounded.Nfc
BleInfo.Type.THERMOMETER -> Icons.Rounded.Thermostat BleInfo.Type.THERMOMETER -> Icons.Rounded.Thermostat
BleInfo.Type.ACCELEROMETER -> Icons.Rounded.Speed BleInfo.Type.ACCELEROMETER -> Icons.Rounded.Speed
@ -272,6 +274,28 @@ private fun BleItem(
) )
} }
if(ble.recordEnabled){
Surface(
shape = CircleShape,
color = color,
modifier = Modifier.align(Alignment.TopEnd)
) {
Surface(
shape = CircleShape,
color = MaterialTheme.colorScheme.error,
modifier = Modifier.size(12.dp).padding(2.dp)
) {
}
}
}
}
Column { Column {
Text(text = ble.name) Text(text = ble.name)
@ -281,21 +305,6 @@ private fun BleItem(
text = ble.serial text = ble.serial
) )
/*Text(
style = MaterialTheme.typography.bodyMedium,
text = String.format("%.3f", (10.0.pow((ble.tx.toDouble() - (ble.rssi?.toDouble() ?: 0.0) - 74) / 20)))
)
Text(
style = MaterialTheme.typography.bodyMedium,
text = String.format("%.3f", (ble.tx.toDouble() - (ble.rssi?.toDouble() ?: 0.0)))
)
Text(
style = MaterialTheme.typography.bodyMedium,
text = ble.tx.toString() + " tx"
)*/
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.alpha(0.7f) modifier = Modifier.alpha(0.7f)

View File

@ -4,6 +4,7 @@ import llc.arma.ble.app.ui.common.ViewEvent
import llc.arma.ble.app.ui.common.ViewSideEffect import llc.arma.ble.app.ui.common.ViewSideEffect
import llc.arma.ble.app.ui.common.ViewState import llc.arma.ble.app.ui.common.ViewState
import llc.arma.ble.app.ui.model.BleView 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.beacon.BeaconContract import llc.arma.ble.app.ui.screen.inspection.beacon.BeaconContract
import llc.arma.ble.app.ui.screen.inspection.thermometer.ThermometerContract import llc.arma.ble.app.ui.screen.inspection.thermometer.ThermometerContract
import llc.arma.ble.domain.common.BleException import llc.arma.ble.domain.common.BleException
@ -26,6 +27,11 @@ class ConnectionContract {
val event: ThermometerContract.Effect.Navigation val event: ThermometerContract.Effect.Navigation
) : Event() ) : Event()
data class OnAccelNavigationEvent(
val event: AccelerometerContract.Effect.Navigation
) : Event()
} }
sealed class State : ViewState { sealed class State : ViewState {

View File

@ -20,6 +20,7 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import llc.arma.ble.app.ui.model.BleView import llc.arma.ble.app.ui.model.BleView
import llc.arma.ble.app.ui.screen.BleInfoView import llc.arma.ble.app.ui.screen.BleInfoView
import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerContract
import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerScreen import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerScreen
import llc.arma.ble.app.ui.screen.inspection.beacon.BeaconScreen import llc.arma.ble.app.ui.screen.inspection.beacon.BeaconScreen
import llc.arma.ble.app.ui.screen.password.ChangePasswordContract import llc.arma.ble.app.ui.screen.password.ChangePasswordContract
@ -117,7 +118,11 @@ fun ConnectionScreen(
} }
is Ble.Accelerometer -> { is Ble.Accelerometer -> {
AccelerometerScreen(ble = state.ble) AccelerometerScreen(ble = state.ble) {
viewModel.setEvent(
ConnectionContract.Event.OnAccelNavigationEvent(it)
)
}
} }
} }

View File

@ -10,6 +10,7 @@ import llc.arma.ble.app.ui.common.BaseViewModel
import llc.arma.ble.app.ui.mapper.BleMapper import llc.arma.ble.app.ui.mapper.BleMapper
import llc.arma.ble.app.ui.mapper.BleViewMapper import llc.arma.ble.app.ui.mapper.BleViewMapper
import llc.arma.ble.app.ui.model.BleView import llc.arma.ble.app.ui.model.BleView
import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerContract
import llc.arma.ble.app.ui.screen.inspection.beacon.BeaconContract import llc.arma.ble.app.ui.screen.inspection.beacon.BeaconContract
import llc.arma.ble.app.ui.screen.inspection.thermometer.ThermometerContract import llc.arma.ble.app.ui.screen.inspection.thermometer.ThermometerContract
import llc.arma.ble.domain.model.Ble import llc.arma.ble.domain.model.Ble
@ -35,6 +36,7 @@ class ConnectionViewModel @Inject constructor(
is ConnectionContract.Event.OnNavigateUp -> reduce(viewState.value, event) is ConnectionContract.Event.OnNavigateUp -> reduce(viewState.value, event)
is ConnectionContract.Event.OnThermometerNavigationEvent -> reduce(viewState.value, event) is ConnectionContract.Event.OnThermometerNavigationEvent -> reduce(viewState.value, event)
is ConnectionContract.Event.RefreshBle -> reduce(viewState.value, event) is ConnectionContract.Event.RefreshBle -> reduce(viewState.value, event)
is ConnectionContract.Event.OnAccelNavigationEvent -> reduce(viewState.value, event)
} }
} }
@ -74,6 +76,19 @@ class ConnectionViewModel @Inject constructor(
} }
} }
private fun reduce(
state: ConnectionContract.State,
event: ConnectionContract.Event.OnAccelNavigationEvent
) {
when(event.event){
AccelerometerContract.Effect.Navigation.NavigateToChangePassword -> {
setEffect {
ConnectionContract.Effect.Navigation.NavigateToChangePassword(savedStateHandle.get<String>("serial")!!)
}
}
}
}
private fun reduce( private fun reduce(
state: ConnectionContract.State, state: ConnectionContract.State,
event: ConnectionContract.Event.OnNavigateUp event: ConnectionContract.Event.OnNavigateUp

View File

@ -4,6 +4,7 @@ import llc.arma.ble.app.ui.common.ViewEvent
import llc.arma.ble.app.ui.common.ViewSideEffect import llc.arma.ble.app.ui.common.ViewSideEffect
import llc.arma.ble.app.ui.common.ViewState import llc.arma.ble.app.ui.common.ViewState
import llc.arma.ble.app.ui.model.BleView import llc.arma.ble.app.ui.model.BleView
import llc.arma.ble.app.ui.screen.inspection.beacon.BeaconContract
import llc.arma.ble.domain.model.Ble import llc.arma.ble.domain.model.Ble
import llc.arma.ble.domain.usecase.AccelScale import llc.arma.ble.domain.usecase.AccelScale
import llc.arma.ble.domain.usecase.AccelViewMode import llc.arma.ble.domain.usecase.AccelViewMode
@ -15,12 +16,15 @@ class AccelerometerContract {
sealed class Event : ViewEvent { sealed class Event : ViewEvent {
object OnShowAccelerometerMeasure : Event() object OnShowAccelerometerAccel : Event()
object OnHideAccelerometerMeasure : Event() object OnHideAccelerometerAccel : Event()
object OnShowAccelerometerSpectre : Event() object OnShowAccelerometerSpectre : Event()
object OnHideAccelerometerSpectre : Event() object OnHideAccelerometerSpectre : Event()
object OnShowAccelerometerHistory : Event()
object OnHideAccelerometerHistory : Event()
data class OnAccelViewModeEdit( data class OnAccelViewModeEdit(
val next: Next val next: Next
) : Event(){ ) : Event(){
@ -44,6 +48,9 @@ class AccelerometerContract {
object OnHideWriteBlePreview : Event() object OnHideWriteBlePreview : Event()
object OnChangePassword : Event()
object OnSaveIntervalEdit : Event()
data class OnBleChanged( data class OnBleChanged(
val ble: Ble.Accelerometer, val ble: Ble.Accelerometer,
): Event() ): Event()
@ -68,6 +75,14 @@ class AccelerometerContract {
val mode: FftViewMode val mode: FftViewMode
) : Event() ) : Event()
data class OnSaveHistoryChanged(
val save: Boolean
) : Event()
data class OnSaveIntervalChanged(
val interval: Long
) : Event()
} }
sealed class State : ViewState { sealed class State : ViewState {
@ -107,7 +122,8 @@ class AccelerometerContract {
sealed class Effect : ViewSideEffect { sealed class Effect : ViewSideEffect {
object ShowAccelerometerMeasure : Effect() object ShowAccelerometerAccel : Effect()
object ShowAccelerometerSpectre : Effect()
object ShowAccelerometerHistory : Effect() object ShowAccelerometerHistory : Effect()
object ShowPowerPicker : Effect() object ShowPowerPicker : Effect()
@ -125,6 +141,15 @@ class AccelerometerContract {
object ShowFftAxisEdit : Effect() object ShowFftAxisEdit : Effect()
object ShowFftModeEdit : Effect() object ShowFftModeEdit : Effect()
object HideIntervalPicker : Effect()
object ShowIntervalPicker : Effect()
sealed class Navigation : Effect() {
object NavigateToChangePassword : Navigation()
}
} }
} }

View File

@ -1,6 +1,5 @@
package llc.arma.ble.app.ui.screen.inspection.accelerometer package llc.arma.ble.app.ui.screen.inspection.accelerometer
import android.util.Log
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ModalBottomSheetValue import androidx.compose.material.ModalBottomSheetValue
@ -23,22 +22,25 @@ import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelFftModeEdit
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelFrequencyEdit import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelFrequencyEdit
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelSpectreEdit import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelSpectreEdit
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelViewEdit import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelViewEdit
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelerometerSpectre
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelerometerAccel
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.AccelerometerHistory 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.DisplayState
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.IntervalEdit
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.LoadingState import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.LoadingState
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.PowerEdit import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.PowerEdit
import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.Write import llc.arma.ble.app.ui.screen.inspection.accelerometer.view.Write
import llc.arma.ble.domain.model.Ble import llc.arma.ble.domain.model.Ble
enum class SheetPage { enum class SheetPage {
MEASURE, POWER, WRITE, HISTORY, ACCEL_MODE_EDIT, SPECTRE_MODE_EDIT, SPECTRE_EDIT, FREQUENCY_EDIT, AXIS_EDIT, FFT_MODE_EDIT HISTORY, ACCEL, POWER, WRITE, SPECTRE, ACCEL_MODE_EDIT, SPECTRE_MODE_EDIT, SPECTRE_EDIT, FREQUENCY_EDIT, AXIS_EDIT, FFT_MODE_EDIT, INTERVAL_EDIT
} }
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
fun AccelerometerScreen( fun AccelerometerScreen(
ble: Ble.Accelerometer, ble: Ble.Accelerometer,
onEvent: (AccelerometerContract.Effect.Navigation) -> Unit
) { ) {
val viewModel = hiltViewModel<AccelerometerViewModel>() val viewModel = hiltViewModel<AccelerometerViewModel>()
@ -57,9 +59,7 @@ fun AccelerometerScreen(
LaunchedEffect( LaunchedEffect(
key1 = bottomDialog.sheetState?.currentValue, key1 = bottomDialog.sheetState?.currentValue,
block = { block = {
Log.d("sheet", bottomDialog.sheetState?.currentValue.toString())
if(bottomDialog.sheetState?.currentValue == ModalBottomSheetValue.Hidden) { if(bottomDialog.sheetState?.currentValue == ModalBottomSheetValue.Hidden) {
Log.d("sheet", "dispose")
bottomDialog.setContent({}) bottomDialog.setContent({})
sheetPage = null sheetPage = null
} }
@ -70,12 +70,12 @@ fun AccelerometerScreen(
LaunchedEffect(sheetPage) { LaunchedEffect(sheetPage) {
when (sheetPage) { when (sheetPage) {
SheetPage.MEASURE -> launch { SheetPage.HISTORY -> launch {
val currentState = viewModel.viewState.value val currentState = viewModel.viewState.value
if (currentState is AccelerometerContract.State.Display) { if (currentState is AccelerometerContract.State.Display) {
bottomDialog.show { bottomDialog.show {
AccelerometerMeasure( AccelerometerHistory(
ble = currentState.accelerometer.info, ble = currentState.accelerometer.info,
accelMode = currentState.accelViewMode, accelMode = currentState.accelViewMode,
fftAxis = currentState.fftAxis, fftAxis = currentState.fftAxis,
@ -86,12 +86,28 @@ fun AccelerometerScreen(
} }
} }
} }
SheetPage.HISTORY -> launch { SheetPage.ACCEL -> launch {
val currentState = viewModel.viewState.value val currentState = viewModel.viewState.value
if (currentState is AccelerometerContract.State.Display) { if (currentState is AccelerometerContract.State.Display) {
bottomDialog.show { bottomDialog.show {
AccelerometerHistory( AccelerometerAccel(
ble = currentState.accelerometer.info,
accelMode = currentState.accelViewMode,
fftAxis = currentState.fftAxis,
fftMode = currentState.fftViewMode,
frequency = currentState.fftFrequency,
accelScale = currentState.accelScale
)
}
}
}
SheetPage.SPECTRE -> launch {
val currentState = viewModel.viewState.value
if (currentState is AccelerometerContract.State.Display) {
bottomDialog.show {
AccelerometerSpectre(
ble = currentState.accelerometer.info, ble = currentState.accelerometer.info,
accelMode = currentState.accelViewMode, accelMode = currentState.accelViewMode,
fftAxis = currentState.fftAxis, fftAxis = currentState.fftAxis,
@ -221,9 +237,27 @@ fun AccelerometerScreen(
} }
} }
SheetPage.INTERVAL_EDIT -> bottomDialog.show {
val currentState = viewModel.viewState.value
if(currentState is AccelerometerContract.State.Display) {
IntervalEdit(
state = currentState.accelerometer,
onEvent = {
viewModel.setEvent(it)
}
)
}
}
null -> { null -> {
bottomDialog.hide() bottomDialog.hide()
} }
} }
} }
@ -238,10 +272,10 @@ fun AccelerometerScreen(
LaunchedEffect("effect"){ LaunchedEffect("effect"){
viewModel.effect.onEach { viewModel.effect.onEach {
when(it){ when(it){
is AccelerometerContract.Effect.ShowAccelerometerMeasure -> launch { is AccelerometerContract.Effect.ShowAccelerometerAccel -> launch {
sheetPage = null sheetPage = null
delay(100) delay(100)
sheetPage = SheetPage.MEASURE sheetPage = SheetPage.ACCEL
} }
is AccelerometerContract.Effect.HidePowerPicker -> launch { is AccelerometerContract.Effect.HidePowerPicker -> launch {
sheetPage = null sheetPage = null
@ -261,10 +295,10 @@ fun AccelerometerScreen(
delay(100) delay(100)
sheetPage = SheetPage.WRITE sheetPage = SheetPage.WRITE
} }
is AccelerometerContract.Effect.ShowAccelerometerHistory -> launch { is AccelerometerContract.Effect.ShowAccelerometerSpectre -> launch {
sheetPage = null sheetPage = null
delay(100) delay(100)
sheetPage = SheetPage.HISTORY sheetPage = SheetPage.SPECTRE
} }
is AccelerometerContract.Effect.ShowAccelEdit -> launch { is AccelerometerContract.Effect.ShowAccelEdit -> launch {
sheetPage = null sheetPage = null
@ -294,6 +328,25 @@ fun AccelerometerScreen(
delay(100) delay(100)
sheetPage = SheetPage.FFT_MODE_EDIT sheetPage = SheetPage.FFT_MODE_EDIT
} }
AccelerometerContract.Effect.ShowAccelerometerHistory -> launch {
sheetPage = null
delay(100)
sheetPage = SheetPage.HISTORY
}
AccelerometerContract.Effect.HideIntervalPicker -> {
sheetPage = null
delay(100)
}
AccelerometerContract.Effect.ShowIntervalPicker -> {
sheetPage = null
delay(100)
sheetPage = SheetPage.INTERVAL_EDIT
}
is AccelerometerContract.Effect.Navigation -> {
onEvent(it)
}
} }
}.launchIn(this) }.launchIn(this)
} }

View File

@ -1,14 +1,14 @@
package llc.arma.ble.app.ui.screen.inspection.accelerometer package llc.arma.ble.app.ui.screen.inspection.accelerometer
import android.util.Log
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import llc.arma.ble.app.ui.common.BaseViewModel import llc.arma.ble.app.ui.common.BaseViewModel
import llc.arma.ble.app.ui.mapper.BleMapper import llc.arma.ble.app.ui.mapper.BleMapper
import llc.arma.ble.app.ui.mapper.BleViewMapper import llc.arma.ble.app.ui.mapper.BleViewMapper
import llc.arma.ble.app.ui.model.BleView import llc.arma.ble.app.ui.model.BleView
import llc.arma.ble.app.ui.screen.inspection.beacon.BeaconContract
import llc.arma.ble.app.ui.screen.inspection.thermometer.ThermometerContract
import llc.arma.ble.domain.model.Ble import llc.arma.ble.domain.model.Ble
import llc.arma.ble.domain.usecase.AccelScale import llc.arma.ble.domain.usecase.AccelScale
import llc.arma.ble.domain.usecase.AccelViewMode import llc.arma.ble.domain.usecase.AccelViewMode
@ -30,8 +30,8 @@ class AccelerometerViewModel @Inject constructor(
override fun handleEvents(event: AccelerometerContract.Event) { override fun handleEvents(event: AccelerometerContract.Event) {
when(event){ when(event){
is AccelerometerContract.Event.OnBleChanged -> reduce(viewState.value, event) is AccelerometerContract.Event.OnBleChanged -> reduce(viewState.value, event)
is AccelerometerContract.Event.OnHideAccelerometerMeasure -> reduce(viewState.value, event) is AccelerometerContract.Event.OnHideAccelerometerAccel -> reduce(viewState.value, event)
is AccelerometerContract.Event.OnShowAccelerometerMeasure -> reduce(viewState.value, event) is AccelerometerContract.Event.OnShowAccelerometerAccel -> reduce(viewState.value, event)
is AccelerometerContract.Event.OnPowerChanged -> reduce(viewState.value, event) is AccelerometerContract.Event.OnPowerChanged -> reduce(viewState.value, event)
is AccelerometerContract.Event.OnPowerEdit -> reduce(viewState.value, event) is AccelerometerContract.Event.OnPowerEdit -> reduce(viewState.value, event)
is AccelerometerContract.Event.OnShowWriteBlePreview -> reduce(viewState.value, event) is AccelerometerContract.Event.OnShowWriteBlePreview -> reduce(viewState.value, event)
@ -48,9 +48,62 @@ class AccelerometerViewModel @Inject constructor(
is AccelerometerContract.Event.OnFftModeChanged -> reduce(viewState.value, event) is AccelerometerContract.Event.OnFftModeChanged -> reduce(viewState.value, event)
is AccelerometerContract.Event.OnFftAxisEdit -> reduce(viewState.value, event) is AccelerometerContract.Event.OnFftAxisEdit -> reduce(viewState.value, event)
is AccelerometerContract.Event.OnFftModeEdit -> reduce(viewState.value, event) is AccelerometerContract.Event.OnFftModeEdit -> reduce(viewState.value, event)
is AccelerometerContract.Event.OnSaveHistoryChanged -> reduce(viewState.value, event)
is AccelerometerContract.Event.OnHideAccelerometerHistory -> reduce(viewState.value, event)
is AccelerometerContract.Event.OnShowAccelerometerHistory -> reduce(viewState.value, event)
is AccelerometerContract.Event.OnChangePassword -> reduce(viewState.value, event)
is AccelerometerContract.Event.OnSaveIntervalChanged -> reduce(viewState.value, event)
is AccelerometerContract.Event.OnSaveIntervalEdit -> reduce(viewState.value, event)
} }
} }
private fun reduce(
state: AccelerometerContract.State,
event: AccelerometerContract.Event.OnSaveIntervalEdit
) {
setEffect {
AccelerometerContract.Effect.ShowIntervalPicker
}
}
private fun reduce(
state: AccelerometerContract.State,
event: AccelerometerContract.Event.OnSaveIntervalChanged
) {
if(state is AccelerometerContract.State.Display) {
state.accelerometer.accelerometerState.historyInterval = event.interval
}
setEffect {
AccelerometerContract.Effect.HideIntervalPicker
}
}
private fun reduce(
state: AccelerometerContract.State,
event: AccelerometerContract.Event.OnChangePassword
) {
setEffect {
AccelerometerContract.Effect.Navigation.NavigateToChangePassword
}
}
private fun reduce(
state: AccelerometerContract.State,
event: AccelerometerContract.Event.OnSaveHistoryChanged
) {
if(state is AccelerometerContract.State.Display) {
state.accelerometer.accelerometerState.saveHistory = event.save
}
}
private fun reduce( private fun reduce(
state: AccelerometerContract.State, state: AccelerometerContract.State,
event: AccelerometerContract.Event.OnFftModeEdit event: AccelerometerContract.Event.OnFftModeEdit
@ -152,7 +205,7 @@ class AccelerometerViewModel @Inject constructor(
) { ) {
setEffect { setEffect {
AccelerometerContract.Effect.ShowAccelerometerHistory AccelerometerContract.Effect.ShowAccelerometerSpectre
} }
} }
@ -164,6 +217,26 @@ class AccelerometerViewModel @Inject constructor(
}
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( private fun reduce(
@ -197,7 +270,9 @@ class AccelerometerViewModel @Inject constructor(
val newBle = bleViewMapper.map(state.accelerometer) as Ble.Accelerometer val newBle = bleViewMapper.map(state.accelerometer) as Ble.Accelerometer
val writeRequest = Ble.Accelerometer.WriteRequest( val writeRequest = Ble.Accelerometer.WriteRequest(
tx = if(newBle.state.tx == state.origin.state.tx) null else newBle.state.tx tx = if(newBle.state.tx == state.origin.state.tx) null else newBle.state.tx,
saveHistory = if(newBle.accelerometerState.saveHistory == state.origin.accelerometerState.saveHistory) null else newBle.accelerometerState.saveHistory,
historyInterval = if(newBle.accelerometerState.historyInterval == state.origin.accelerometerState.historyInterval) null else newBle.accelerometerState.historyInterval,
) )
setState { setState {
@ -274,7 +349,7 @@ class AccelerometerViewModel @Inject constructor(
private fun reduce( private fun reduce(
state: AccelerometerContract.State, state: AccelerometerContract.State,
event: AccelerometerContract.Event.OnHideAccelerometerMeasure event: AccelerometerContract.Event.OnHideAccelerometerAccel
) { ) {
@ -283,13 +358,13 @@ class AccelerometerViewModel @Inject constructor(
private fun reduce( private fun reduce(
state: AccelerometerContract.State, state: AccelerometerContract.State,
event: AccelerometerContract.Event.OnShowAccelerometerMeasure event: AccelerometerContract.Event.OnShowAccelerometerAccel
) { ) {
viewModelScope.launch { viewModelScope.launch {
setEffect { setEffect {
AccelerometerContract.Effect.ShowAccelerometerMeasure AccelerometerContract.Effect.ShowAccelerometerAccel
} }
} }
@ -308,7 +383,8 @@ class AccelerometerViewModel @Inject constructor(
state.copy( state.copy(
origin = Ble.Accelerometer( origin = Ble.Accelerometer(
info = event.ble.info, info = event.ble.info,
state = event.ble.state state = event.ble.state,
accelerometerState = state.origin.accelerometerState
) )
) )
} }
@ -361,6 +437,10 @@ class AccelerometerViewModel @Inject constructor(
info = currentState.origin.info, info = currentState.origin.info,
state = currentState.origin.state.copy( state = currentState.origin.state.copy(
tx = request.writeRequest.tx ?: state.origin.state.tx tx = request.writeRequest.tx ?: state.origin.state.tx
),
accelerometerState = currentState.origin.accelerometerState.copy(
saveHistory = request.writeRequest.saveHistory
?: currentState.origin.accelerometerState.saveHistory
) )
) )

View File

@ -13,9 +13,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp 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.accelerometer.AccelerometerContract
import llc.arma.ble.app.ui.screen.inspection.thermometer.ThermometerContract
import llc.arma.ble.domain.usecase.AccelViewMode import llc.arma.ble.domain.usecase.AccelViewMode
import llc.arma.ble.domain.usecase.AccelViewMode.* import llc.arma.ble.domain.usecase.AccelViewMode.*
@ -90,7 +88,7 @@ fun AccelViewEdit(
onEvent(AccelerometerContract.Event.OnAccelViewModelChanged(value)) onEvent(AccelerometerContract.Event.OnAccelViewModelChanged(value))
when(next){ when(next){
AccelerometerContract.Event.OnAccelViewModeEdit.Next.ACCEL -> { AccelerometerContract.Event.OnAccelViewModeEdit.Next.ACCEL -> {
onEvent(AccelerometerContract.Event.OnShowAccelerometerMeasure) onEvent(AccelerometerContract.Event.OnShowAccelerometerAccel)
} }
AccelerometerContract.Event.OnAccelViewModeEdit.Next.SPECTRE -> { AccelerometerContract.Event.OnAccelViewModeEdit.Next.SPECTRE -> {
onEvent(AccelerometerContract.Event.OnSpectreEdit) onEvent(AccelerometerContract.Event.OnSpectreEdit)

View File

@ -0,0 +1,499 @@
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
import android.util.Log
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
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.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 com.patrykandpatrick.vico.core.entry.FloatEntry
import kotlinx.coroutines.Job
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.AccelScale
import llc.arma.ble.domain.usecase.AccelViewMode
import llc.arma.ble.domain.usecase.FftAxis
import llc.arma.ble.domain.usecase.FftFrequency
import llc.arma.ble.domain.usecase.FftViewMode
import llc.arma.ble.domain.usecase.GetAccelerometerSpectreBySerial
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 AccelerometerSpectre(
ble: BleInfo,
accelMode: AccelViewMode,
fftAxis: FftAxis,
fftMode: FftViewMode,
frequency: FftFrequency,
accelScale: AccelScale
) {
val viewModel = hiltViewModel<AccelerometerSpectreViewModel>()
val state = viewModel.viewState.value
LaunchedEffect(ble.serial, accelMode, fftAxis, fftMode, frequency) {
viewModel.setEvent(AccelerometerSpectreContract.Event.OnStart(ble.serial, accelMode, fftAxis, fftMode, frequency, accelScale))
}
DisposableEffect(key1 = "ble", effect = {
onDispose {
Log.d("history", "dispose")
viewModel.setEvent(AccelerometerSpectreContract.Event.StopMeasure)
}
})
Column(
modifier = Modifier.fillMaxHeight(0.9f)
) {
Row(
modifier = Modifier.padding(horizontal = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
val title = when(state){
is AccelerometerSpectreContract.State.Display -> {
if (state.previousHistory !== null) {
"${fftMode.localized} (${state.previousHistory.size})"
}else {
fftMode.localized
}
}
AccelerometerSpectreContract.State.Exception -> fftMode.localized
}
Row(
modifier = Modifier.weight(1f),
verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier.padding(end = 16.dp),
text = title,
style = MaterialTheme.typography.titleLarge
)
if(state is AccelerometerSpectreContract.State.Display) {
when (state.loadingHistoryState) {
is ProgressState.Indeterminate -> {
CircularProgressIndicator(
modifier = Modifier.size(16.dp),
strokeWidth = 2.dp,
strokeCap = StrokeCap.Round,
)
}
is ProgressState.Progress -> {
val progressAnimDuration = 1500
val progressAnimation by animateFloatAsState(
targetValue = state.loadingHistoryState.value,
animationSpec = tween(
durationMillis = progressAnimDuration,
easing = FastOutSlowInEasing
)
)
CircularProgressIndicator(
modifier = Modifier.size(16.dp),
strokeWidth = 2.dp,
strokeCap = StrokeCap.Round,
progress = progressAnimation,
)
}
else -> {}
}
}
}
IconButton(
onClick = {
viewModel.setEvent(AccelerometerSpectreContract.Event.OnStart(ble.serial, accelMode, fftAxis, fftMode, frequency, accelScale))
},
enabled = when(state){
is AccelerometerSpectreContract.State.Display -> state.loadingHistoryState is ProgressState.Finished
AccelerometerSpectreContract.State.Exception -> true
}
) {
Icon(
imageVector = Icons.Rounded.Refresh,
contentDescription = null
)
}
}
Spacer(modifier = Modifier.height(16.dp))
Box(modifier = Modifier) {
when (state) {
is AccelerometerSpectreContract.State.Display -> Display(state = state)
AccelerometerSpectreContract.State.Exception -> Exception()
}
}
}
}
val axisValueFormatter = AxisValueFormatter<AxisPosition.Horizontal.Bottom> { value, chartValues ->
(chartValues.chartEntryModel.entries.firstOrNull()
?.getOrNull(value.toInt()) as? AccelerometerEntry)
?.frequency?.let { String.format("%.1f", (it.toFloat() / 256f)) }
.orEmpty()
}
@Composable
fun Display(
state: AccelerometerSpectreContract.State.Display
) {
Box(modifier = Modifier
.padding(8.dp)
.fillMaxSize()
) {
val data = if(state.loadingHistoryState is ProgressState.Finished){
state.loadingHistoryState.data
} else {
state.previousHistory
}
val producer = remember {
ChartEntryModelProducer(listOf<FloatEntry>())
}
if(data != null){
if(data.isEmpty()){
Text(
modifier = Modifier.align(Alignment.Center),
text = "Нет данных"
)
} else {
LaunchedEffect(data){
producer.setEntries(
data.mapIndexed { index, measurePoint ->
AccelerometerEntry(measurePoint.frequency, index.toFloat(), measurePoint.value)
}
)
}
val lineChart = columnChart(
spacing = 1.5.dp
)
val scrollState = rememberChartScrollState()
Chart(
diffAnimationSpec = tween(0),
isZoomEnabled = true,
chartScrollState = scrollState,
chart = lineChart,
chartModelProducer = producer,
startAxis = startAxis(),
bottomAxis = bottomAxis(
tickLength = 0.dp,
valueFormatter = axisValueFormatter,
labelRotationDegrees = -90f,
),
modifier = Modifier.fillMaxSize()
)
}
} else {
when (state.loadingHistoryState) {
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)
)
}
else -> {}
}
}
}
}
@Composable
private fun Exception(
) {
Box(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
.aspectRatio(2f),
){
Text(
textAlign = TextAlign.Center,
text = "Во время загрузки произошла ошибка",
modifier = Modifier.align(Alignment.Center)
)
}
}
class AccelerometerSpectreContract {
sealed class Event : ViewEvent {
object StopMeasure : Event()
data class OnStart(
val serial: String,
val accelMode: AccelViewMode,
val fftAxis: FftAxis,
val fftMode: FftViewMode,
val frequency: FftFrequency,
val accelScale: AccelScale
) : Event()
data class OnRefreshHistory(
val serial: String,
val accelMode: AccelViewMode,
val fftAxis: FftAxis,
val fftMode: FftViewMode,
val frequency: FftFrequency,
val accelScale: AccelScale
) : Event()
}
sealed class State : ViewState {
data class Display(
val previousHistory : List<Ble.Accelerometer.MeasurePoint>?,
val loadingHistoryState : ProgressState<List<Ble.Accelerometer.MeasurePoint>>
) : State()
object Exception : State()
}
sealed class Effect : ViewSideEffect {
}
}
@HiltViewModel
class AccelerometerSpectreViewModel @Inject constructor(
private val getAccelerometerSpectreBySerial: GetAccelerometerSpectreBySerial
) : BaseViewModel<AccelerometerSpectreContract.State, AccelerometerSpectreContract.Event, AccelerometerSpectreContract.Effect>() {
private var job: Job? = null
private var lastSerial: String? = null
override fun setInitialState() = AccelerometerSpectreContract.State.Display(
loadingHistoryState = ProgressState.Indeterminate,
previousHistory = null
)
override fun handleEvents(event: AccelerometerSpectreContract.Event) {
when(event){
is AccelerometerSpectreContract.Event.OnStart -> reduce(viewState.value, event)
is AccelerometerSpectreContract.Event.OnRefreshHistory -> reduce(viewState.value, event)
is AccelerometerSpectreContract.Event.StopMeasure -> reduce(viewState.value, event)
}
}
private fun reduce(
state: AccelerometerSpectreContract.State,
event: AccelerometerSpectreContract.Event.OnStart
) {
viewModelScope.launch {
if(state is AccelerometerSpectreContract.State.Display) {
//if(lastSerial != event.serial) {
lastSerial = event.serial
setState {
AccelerometerSpectreContract.State.Display(
loadingHistoryState = ProgressState.Indeterminate,
previousHistory = when(state.loadingHistoryState){
is ProgressState.Finished -> state.loadingHistoryState.data
is ProgressState.Indeterminate -> null
is ProgressState.Progress -> null
}
)
}
} else {
setState {
AccelerometerSpectreContract.State.Display(
loadingHistoryState = ProgressState.Indeterminate,
previousHistory = null
)
}
}
job?.cancel()
job = getAccelerometerSpectreBySerial(
serial = event.serial,
accelMode = event.accelMode,
fftAxis = event.fftAxis,
fftMode = event.fftMode,
frequency = event.frequency,
accelScale = event.accelScale
).onEach {
val currentState = viewState.value
if(currentState is AccelerometerSpectreContract.State.Display) {
it.fold(
onSuccess = {
setState {
AccelerometerSpectreContract.State.Display(
loadingHistoryState = it,
previousHistory = when (it) {
is ProgressState.Finished -> {
it.data
}
is ProgressState.Indeterminate -> currentState.previousHistory
is ProgressState.Progress -> currentState.previousHistory
}
)
}
},
onFailure = {
setState {
AccelerometerSpectreContract.State.Exception
}
}
)
}
}.launchIn(this)
}
}
private fun reduce(
state: AccelerometerSpectreContract.State,
event: AccelerometerSpectreContract.Event.OnRefreshHistory
) {
/*viewModelScope.launch {
setState {
AccelerometerHistoryContract.State.Display(ProgressState.Indeterminate)
}
getAccelerometerSpectreBySerial(
serial = event.serial,
accelMode = event.accelMode,
fftAxis = event.fftAxis,
fftMode = event.fftMode,
frequency = event.frequency,
accelScale = event.accelScale
).onEach {
it.fold(
onSuccess = {
setState {
AccelerometerHistoryContract.State.Display(it)
}
},
onFailure = {
setState {
AccelerometerHistoryContract.State.Exception
}
}
)
}.launchIn(this)
}*/
}
private fun reduce(
state: AccelerometerSpectreContract.State,
event: AccelerometerSpectreContract.Event.StopMeasure
) {
job?.cancel()
}
}

View File

@ -1,6 +1,5 @@
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
import android.util.Log
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material3.* import androidx.compose.material3.*
@ -45,7 +44,7 @@ import llc.arma.ble.domain.usecase.FftViewMode
import llc.arma.ble.domain.usecase.GetAccelerometerMeasureBySerialFlow import llc.arma.ble.domain.usecase.GetAccelerometerMeasureBySerialFlow
@Composable @Composable
fun AccelerometerMeasure( fun AccelerometerAccel(
ble: BleInfo, ble: BleInfo,
accelScale: AccelScale, accelScale: AccelScale,
accelMode: AccelViewMode, accelMode: AccelViewMode,
@ -54,20 +53,15 @@ fun AccelerometerMeasure(
frequency: FftFrequency frequency: FftFrequency
) { ) {
val viewModel = hiltViewModel<AccelerometerMeasureViewModel>() val viewModel = hiltViewModel<AccelerometerAccelViewModel>()
val state = viewModel.viewState.value val state = viewModel.viewState.value
/*LaunchedEffect(ble.serial) { viewModel.setEvent(AccelerometerAccelContract.Event.OnStart(ble.serial, accelScale, accelMode, fftAxis, fftMode, frequency))
viewModel.setEvent(AccelerometerMeasureContract.Event.OnStart(ble.serial))
}*/
viewModel.setEvent(AccelerometerMeasureContract.Event.OnStart(ble.serial, accelScale, accelMode, fftAxis, fftMode, frequency))
DisposableEffect(key1 = "ble", effect = { DisposableEffect(key1 = "ble", effect = {
onDispose { onDispose {
Log.d("measure", "dispose") viewModel.setEvent(AccelerometerAccelContract.Event.StopMeasure)
viewModel.setEvent(AccelerometerMeasureContract.Event.StopMeasure)
} }
}) })
@ -89,7 +83,7 @@ fun AccelerometerMeasure(
IconButton( IconButton(
onClick = { onClick = {
viewModel.setEvent(AccelerometerMeasureContract.Event.OnRefreshHistory(ble.serial, accelScale, accelMode, fftAxis, fftMode, frequency)) viewModel.setEvent(AccelerometerAccelContract.Event.OnRefreshHistory(ble.serial, accelScale, accelMode, fftAxis, fftMode, frequency))
}, },
enabled = true enabled = true
) { ) {
@ -106,8 +100,8 @@ fun AccelerometerMeasure(
Box(modifier = Modifier) { Box(modifier = Modifier) {
when (state) { when (state) {
is AccelerometerMeasureContract.State.Display -> Display(state = state) is AccelerometerAccelContract.State.Display -> Display(state = state)
AccelerometerMeasureContract.State.Exception -> Exception() is AccelerometerAccelContract.State.Exception -> Exception()
} }
} }
@ -119,7 +113,7 @@ fun AccelerometerMeasure(
@Composable @Composable
fun Display( fun Display(
state: AccelerometerMeasureContract.State.Display state: AccelerometerAccelContract.State.Display
) { ) {
Box(modifier = Modifier Box(modifier = Modifier
@ -255,7 +249,7 @@ private fun Exception(
} }
class AccelerometerMeasureContract { class AccelerometerAccelContract {
sealed class Event : ViewEvent { sealed class Event : ViewEvent {
@ -300,29 +294,29 @@ class AccelerometerMeasureContract {
@HiltViewModel @HiltViewModel
class AccelerometerMeasureViewModel @Inject constructor( class AccelerometerAccelViewModel @Inject constructor(
private val getAccelerometerMeasureBySerialFlow: GetAccelerometerMeasureBySerialFlow, private val getAccelerometerMeasureBySerialFlow: GetAccelerometerMeasureBySerialFlow,
) : BaseViewModel<AccelerometerMeasureContract.State, AccelerometerMeasureContract.Event, AccelerometerMeasureContract.Effect>() { ) : BaseViewModel<AccelerometerAccelContract.State, AccelerometerAccelContract.Event, AccelerometerAccelContract.Effect>() {
var measureJob: Job? = null var measureJob: Job? = null
private var lastSerial: String? = null private var lastSerial: String? = null
override fun setInitialState() = AccelerometerMeasureContract.State.Display( override fun setInitialState() = AccelerometerAccelContract.State.Display(
emptyList() emptyList()
) )
override fun handleEvents(event: AccelerometerMeasureContract.Event) { override fun handleEvents(event: AccelerometerAccelContract.Event) {
when(event){ when(event){
is AccelerometerMeasureContract.Event.OnStart -> reduce(viewState.value, event) is AccelerometerAccelContract.Event.OnStart -> reduce(viewState.value, event)
is AccelerometerMeasureContract.Event.OnRefreshHistory -> reduce(viewState.value, event) is AccelerometerAccelContract.Event.OnRefreshHistory -> reduce(viewState.value, event)
is AccelerometerMeasureContract.Event.StopMeasure -> reduce(viewState.value, event) is AccelerometerAccelContract.Event.StopMeasure -> reduce(viewState.value, event)
} }
} }
private fun reduce( private fun reduce(
state: AccelerometerMeasureContract.State, state: AccelerometerAccelContract.State,
event: AccelerometerMeasureContract.Event.StopMeasure event: AccelerometerAccelContract.Event.StopMeasure
) { ) {
@ -330,14 +324,14 @@ class AccelerometerMeasureViewModel @Inject constructor(
measureJob = null measureJob = null
setState { setState {
AccelerometerMeasureContract.State.Display(emptyList()) AccelerometerAccelContract.State.Display(emptyList())
} }
} }
private fun reduce( private fun reduce(
state: AccelerometerMeasureContract.State, state: AccelerometerAccelContract.State,
event: AccelerometerMeasureContract.Event.OnStart event: AccelerometerAccelContract.Event.OnStart
) { ) {
startReadMeasure(event.serial, event.accelScale, event.accelMode, event.fftAxis, event.fftMode, event.frequency, false) startReadMeasure(event.serial, event.accelScale, event.accelMode, event.fftAxis, event.fftMode, event.frequency, false)
@ -345,8 +339,8 @@ class AccelerometerMeasureViewModel @Inject constructor(
} }
private fun reduce( private fun reduce(
state: AccelerometerMeasureContract.State, state: AccelerometerAccelContract.State,
event: AccelerometerMeasureContract.Event.OnRefreshHistory event: AccelerometerAccelContract.Event.OnRefreshHistory
) { ) {
startReadMeasure(event.serial, event.accelScale, event.accelMode, event.fftAxis, event.fftMode, event.frequency, true) startReadMeasure(event.serial, event.accelScale, event.accelMode, event.fftAxis, event.fftMode, event.frequency, true)
} }
@ -367,7 +361,7 @@ class AccelerometerMeasureViewModel @Inject constructor(
measureJob = viewModelScope.launch { measureJob = viewModelScope.launch {
setState { setState {
AccelerometerMeasureContract.State.Display(emptyList()) AccelerometerAccelContract.State.Display(emptyList())
} }
getAccelerometerMeasureBySerialFlow(serial, accelScale, accelMode, fftAxis, fftMode, frequency).onEach { getAccelerometerMeasureBySerialFlow(serial, accelScale, accelMode, fftAxis, fftMode, frequency).onEach {
@ -375,7 +369,7 @@ class AccelerometerMeasureViewModel @Inject constructor(
onSuccess = { onSuccess = {
setState { setState {
when (this) { when (this) {
is AccelerometerMeasureContract.State.Display -> { is AccelerometerAccelContract.State.Display -> {
val dataList = this.measureHistory.toMutableList().apply { val dataList = this.measureHistory.toMutableList().apply {
add( add(
Accelerate( Accelerate(
@ -385,18 +379,18 @@ class AccelerometerMeasureViewModel @Inject constructor(
) )
) )
}.takeLast(10) }.takeLast(10)
AccelerometerMeasureContract.State.Display(dataList) AccelerometerAccelContract.State.Display(dataList)
} }
AccelerometerMeasureContract.State.Exception -> { AccelerometerAccelContract.State.Exception -> {
AccelerometerMeasureContract.State.Display(listOf(it)) AccelerometerAccelContract.State.Display(listOf(it))
} }
} }
} }
}, },
onFailure = { onFailure = {
setState { setState {
AccelerometerMeasureContract.State.Exception AccelerometerAccelContract.State.Exception
} }
} }
) )

View File

@ -1,9 +1,9 @@
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
import android.util.Log
import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
@ -12,7 +12,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.viewModelScope 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.axis.vertical.startAxis
import com.patrykandpatrick.vico.compose.chart.Chart import com.patrykandpatrick.vico.compose.chart.Chart
import com.patrykandpatrick.vico.core.entry.ChartEntryModelProducer import com.patrykandpatrick.vico.core.entry.ChartEntryModelProducer
@ -28,60 +27,68 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Refresh import androidx.compose.material.icons.rounded.Refresh
import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import com.patrykandpatrick.vico.compose.chart.column.columnChart import com.patrykandpatrick.vico.compose.axis.horizontal.bottomAxis
import com.patrykandpatrick.vico.compose.chart.line.lineChart
import com.patrykandpatrick.vico.compose.chart.scroll.rememberChartScrollSpec
import com.patrykandpatrick.vico.compose.chart.scroll.rememberChartScrollState import com.patrykandpatrick.vico.compose.chart.scroll.rememberChartScrollState
import com.patrykandpatrick.vico.core.axis.AxisPosition import com.patrykandpatrick.vico.core.axis.AxisPosition
import com.patrykandpatrick.vico.core.axis.formatter.AxisValueFormatter import com.patrykandpatrick.vico.core.axis.formatter.AxisValueFormatter
import com.patrykandpatrick.vico.core.chart.decoration.ThresholdLine
import com.patrykandpatrick.vico.core.chart.scale.AutoScaleUp
import com.patrykandpatrick.vico.core.entry.ChartEntry import com.patrykandpatrick.vico.core.entry.ChartEntry
import com.patrykandpatrick.vico.core.entry.FloatEntry import com.patrykandpatrick.vico.core.entry.FloatEntry
import com.patrykandpatrick.vico.core.extension.sumByFloat import com.patrykandpatrick.vico.core.scroll.AutoScrollCondition
import com.patrykandpatrick.vico.core.scroll.InitialScroll
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import llc.arma.ble.app.ui.screen.inspection.thermometer.view.TemperatureEntry
import llc.arma.ble.app.ui.screen.inspection.thermometer.view.TemperatureHistoryContract
import llc.arma.ble.app.ui.screen.inspection.thermometer.view.formatter
import llc.arma.ble.domain.common.ProgressState import llc.arma.ble.domain.common.ProgressState
import llc.arma.ble.domain.model.Ble import llc.arma.ble.domain.model.Ble
import llc.arma.ble.domain.usecase.AccelScale import llc.arma.ble.domain.usecase.AccelScale
import llc.arma.ble.domain.usecase.AccelViewMode import llc.arma.ble.domain.usecase.AccelViewMode
import llc.arma.ble.domain.usecase.Accelerate
import llc.arma.ble.domain.usecase.FftAxis import llc.arma.ble.domain.usecase.FftAxis
import llc.arma.ble.domain.usecase.FftFrequency import llc.arma.ble.domain.usecase.FftFrequency
import llc.arma.ble.domain.usecase.FftViewMode import llc.arma.ble.domain.usecase.FftViewMode
import llc.arma.ble.domain.usecase.GetAccelerometerSpectreBySerial import llc.arma.ble.domain.usecase.GetAccelerometerHistoryBySerial
import llc.arma.ble.domain.usecase.GetAccelerometerMeasureBySerialFlow
import java.util.Date
class AccelerometerEntry( class AccelEntry(
val frequency: Long, val localDate: Long,
override val x: Float, override val x: Float,
override val y: Float, override val y: Float,
) : ChartEntry { ) : ChartEntry {
override fun withY(y: Float) = AccelerometerEntry(frequency, x, y) override fun withY(y: Float) = AccelEntry(localDate, x, y)
} }
@Composable @Composable
fun AccelerometerHistory( fun AccelerometerHistory(
ble: BleInfo, ble: BleInfo,
accelScale: AccelScale,
accelMode: AccelViewMode, accelMode: AccelViewMode,
fftAxis: FftAxis, fftAxis: FftAxis,
fftMode: FftViewMode, fftMode: FftViewMode,
frequency: FftFrequency, frequency: FftFrequency
accelScale: AccelScale
) { ) {
val viewModel = hiltViewModel<AccelerometerHistoryViewModel>() val viewModel = hiltViewModel<AccelerometerHistoryViewModel>()
val state = viewModel.viewState.value val state = viewModel.viewState.value
LaunchedEffect(ble.serial, accelMode, fftAxis, fftMode, frequency) { LaunchedEffect(ble.serial) {
viewModel.setEvent(AccelerometerHistoryContract.Event.OnStart(ble.serial, accelMode, fftAxis, fftMode, frequency, accelScale)) viewModel.setEvent(AccelerometerHistoryContract.Event.OnStart(ble.serial, accelScale, accelMode, fftAxis, fftMode, frequency))
} }
DisposableEffect(key1 = "ble", effect = { DisposableEffect("ble") {
onDispose { onDispose {
Log.d("history", "dispose")
viewModel.setEvent(AccelerometerHistoryContract.Event.StopMeasure) viewModel.setEvent(AccelerometerHistoryContract.Event.StopMeasure)
} }
}
})
Column( Column(
modifier = Modifier.fillMaxHeight(0.9f) modifier = Modifier.fillMaxHeight(0.9f)
@ -94,67 +101,24 @@ fun AccelerometerHistory(
val title = when(state){ val title = when(state){
is AccelerometerHistoryContract.State.Display -> { is AccelerometerHistoryContract.State.Display -> {
if (state.previousHistory !== null) { when (state.loadingHistoryState) {
"${fftMode.localized} (${state.previousHistory.size})" is ProgressState.Finished -> "График измерений (${state.loadingHistoryState.data.size})"
}else { is ProgressState.Indeterminate -> "График измерений"
fftMode.localized is ProgressState.Progress -> "График измерений"
} }
} }
AccelerometerHistoryContract.State.Exception -> fftMode.localized AccelerometerHistoryContract.State.Exception -> "График измерений"
} }
Row(
modifier = Modifier.weight(1f),
verticalAlignment = Alignment.CenterVertically
) {
Text( Text(
modifier = Modifier.padding(end = 16.dp), modifier = Modifier.weight(1f),
text = title, text = title,
style = MaterialTheme.typography.titleLarge style = MaterialTheme.typography.titleLarge
) )
if(state is AccelerometerHistoryContract.State.Display) {
when (state.loadingHistoryState) {
is ProgressState.Indeterminate -> {
CircularProgressIndicator(
modifier = Modifier.size(16.dp),
strokeWidth = 2.dp,
strokeCap = StrokeCap.Round,
)
}
is ProgressState.Progress -> {
val progressAnimDuration = 1500
val progressAnimation by animateFloatAsState(
targetValue = state.loadingHistoryState.value,
animationSpec = tween(
durationMillis = progressAnimDuration,
easing = FastOutSlowInEasing
)
)
CircularProgressIndicator(
modifier = Modifier.size(16.dp),
strokeWidth = 2.dp,
strokeCap = StrokeCap.Round,
progress = progressAnimation,
)
}
else -> {}
}
}
}
IconButton( IconButton(
onClick = { onClick = {
viewModel.setEvent(AccelerometerHistoryContract.Event.OnStart(ble.serial, accelMode, fftAxis, fftMode, frequency, accelScale)) viewModel.setEvent(AccelerometerHistoryContract.Event.OnRefreshHistory(ble.serial, accelScale, accelMode, fftAxis, fftMode, frequency))
}, },
enabled = when(state){ enabled = when(state){
is AccelerometerHistoryContract.State.Display -> state.loadingHistoryState is ProgressState.Finished is AccelerometerHistoryContract.State.Display -> state.loadingHistoryState is ProgressState.Finished
@ -175,7 +139,7 @@ fun AccelerometerHistory(
when (state) { when (state) {
is AccelerometerHistoryContract.State.Display -> Display(state = state) is AccelerometerHistoryContract.State.Display -> Display(state = state)
AccelerometerHistoryContract.State.Exception -> Exception() is AccelerometerHistoryContract.State.Exception -> Exception()
} }
} }
@ -184,13 +148,6 @@ fun AccelerometerHistory(
} }
val axisValueFormatter = AxisValueFormatter<AxisPosition.Horizontal.Bottom> { value, chartValues ->
(chartValues.chartEntryModel.entries.firstOrNull()
?.getOrNull(value.toInt()) as? AccelerometerEntry)
?.frequency?.let { String.format("%.1f", (it.toFloat() / 256f)) }
.orEmpty()
}
@Composable @Composable
fun Display( fun Display(
@ -202,19 +159,11 @@ fun Display(
.fillMaxSize() .fillMaxSize()
) { ) {
val data = if(state.loadingHistoryState is ProgressState.Finished){ when (state.loadingHistoryState) {
state.loadingHistoryState.data
} else {
state.previousHistory
}
val producer = remember { is ProgressState.Finished -> {
ChartEntryModelProducer(listOf<FloatEntry>())
}
if(data != null){ if(state.loadingHistoryState.data.isEmpty()){
if(data.isEmpty()){
Text( Text(
modifier = Modifier.align(Alignment.Center), modifier = Modifier.align(Alignment.Center),
@ -223,24 +172,26 @@ fun Display(
} else { } else {
LaunchedEffect(data){ val producer = remember {
producer.setEntries( ChartEntryModelProducer(listOf<FloatEntry>())
data.mapIndexed { index, measurePoint ->
AccelerometerEntry(measurePoint.frequency, index.toFloat(), measurePoint.value)
}
)
} }
val lineChart = columnChart( producer.setEntries(state.loadingHistoryState.data.mapIndexed { index, measurePoint ->
spacing = 1.5.dp AccelEntry(measurePoint.frequency, index.toFloat(), measurePoint.value )
) })
val scrollState = rememberChartScrollState() val axisValueFormatter =
AxisValueFormatter<AxisPosition.Horizontal.Bottom> { value, chartValues ->
(chartValues.chartEntryModel.entries.firstOrNull()
?.getOrNull(value.toInt()) as? AccelEntry)
?.localDate
?.let { formatter.format(Date(it)) }
.orEmpty()
}
val lineChart = lineChart()
Chart( Chart(
diffAnimationSpec = tween(0),
isZoomEnabled = true,
chartScrollState = scrollState,
chart = lineChart, chart = lineChart,
chartModelProducer = producer, chartModelProducer = producer,
startAxis = startAxis(), startAxis = startAxis(),
@ -249,14 +200,17 @@ fun Display(
valueFormatter = axisValueFormatter, valueFormatter = axisValueFormatter,
labelRotationDegrees = -90f, labelRotationDegrees = -90f,
), ),
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize(),
chartScrollSpec = rememberChartScrollSpec(
initialScroll = InitialScroll.End,
autoScrollCondition = AutoScrollCondition.OnModelSizeIncreased,
autoScrollAnimationSpec = tween(0)
)
) )
} }
} else { }
when (state.loadingHistoryState) {
is ProgressState.Indeterminate -> { is ProgressState.Indeterminate -> {
CircularProgressIndicator( CircularProgressIndicator(
@ -273,7 +227,7 @@ fun Display(
animationSpec = tween( animationSpec = tween(
durationMillis = progressAnimDuration, durationMillis = progressAnimDuration,
easing = FastOutSlowInEasing easing = FastOutSlowInEasing
) ), label = ""
) )
CircularProgressIndicator( CircularProgressIndicator(
@ -283,17 +237,15 @@ fun Display(
) )
} }
else -> {}
}
} }
} }
} }
@Composable @Composable
private fun Exception( private fun Exception() {
) {
Box( Box(
modifier = Modifier modifier = Modifier
.padding(8.dp) .padding(8.dp)
@ -319,20 +271,20 @@ class AccelerometerHistoryContract {
data class OnStart( data class OnStart(
val serial: String, val serial: String,
val accelScale: AccelScale,
val accelMode: AccelViewMode, val accelMode: AccelViewMode,
val fftAxis: FftAxis, val fftAxis: FftAxis,
val fftMode: FftViewMode, val fftMode: FftViewMode,
val frequency: FftFrequency, val frequency: FftFrequency
val accelScale: AccelScale
) : Event() ) : Event()
data class OnRefreshHistory( data class OnRefreshHistory(
val serial: String, val serial: String,
val accelScale: AccelScale,
val accelMode: AccelViewMode, val accelMode: AccelViewMode,
val fftAxis: FftAxis, val fftAxis: FftAxis,
val fftMode: FftViewMode, val fftMode: FftViewMode,
val frequency: FftFrequency, val frequency: FftFrequency
val accelScale: AccelScale
) : Event() ) : Event()
} }
@ -340,7 +292,6 @@ class AccelerometerHistoryContract {
sealed class State : ViewState { sealed class State : ViewState {
data class Display( data class Display(
val previousHistory : List<Ble.Accelerometer.MeasurePoint>?,
val loadingHistoryState : ProgressState<List<Ble.Accelerometer.MeasurePoint>> val loadingHistoryState : ProgressState<List<Ble.Accelerometer.MeasurePoint>>
) : State() ) : State()
@ -358,15 +309,15 @@ class AccelerometerHistoryContract {
@HiltViewModel @HiltViewModel
class AccelerometerHistoryViewModel @Inject constructor( class AccelerometerHistoryViewModel @Inject constructor(
private val getAccelerometerSpectreBySerial: GetAccelerometerSpectreBySerial private val getAccelerometerHistoryBySerial: GetAccelerometerHistoryBySerial,
) : BaseViewModel<AccelerometerHistoryContract.State, AccelerometerHistoryContract.Event, AccelerometerHistoryContract.Effect>() { ) : BaseViewModel<AccelerometerHistoryContract.State, AccelerometerHistoryContract.Event, AccelerometerHistoryContract.Effect>() {
private var job: Job? = null var measureJob: Job? = null
private var lastSerial: String? = null private var lastSerial: String? = null
override fun setInitialState() = AccelerometerHistoryContract.State.Display( override fun setInitialState() = AccelerometerHistoryContract.State.Display(
loadingHistoryState = ProgressState.Indeterminate, ProgressState.Indeterminate
previousHistory = null
) )
override fun handleEvents(event: AccelerometerHistoryContract.Event) { override fun handleEvents(event: AccelerometerHistoryContract.Event) {
@ -377,6 +328,21 @@ class AccelerometerHistoryViewModel @Inject constructor(
} }
} }
private fun reduce(
state: AccelerometerHistoryContract.State,
event: AccelerometerHistoryContract.Event.StopMeasure
) {
measureJob?.cancel()
measureJob = null
setState {
AccelerometerHistoryContract.State.Exception
}
}
private fun reduce( private fun reduce(
state: AccelerometerHistoryContract.State, state: AccelerometerHistoryContract.State,
event: AccelerometerHistoryContract.Event.OnStart event: AccelerometerHistoryContract.Event.OnStart
@ -386,95 +352,18 @@ class AccelerometerHistoryViewModel @Inject constructor(
if(state is AccelerometerHistoryContract.State.Display) { if(state is AccelerometerHistoryContract.State.Display) {
//if(lastSerial != event.serial) { if(lastSerial != event.serial) {
lastSerial = event.serial lastSerial = event.serial
setState {
AccelerometerHistoryContract.State.Display(
loadingHistoryState = ProgressState.Indeterminate,
previousHistory = when(state.loadingHistoryState){
is ProgressState.Finished -> state.loadingHistoryState.data
is ProgressState.Indeterminate -> null
is ProgressState.Progress -> null
}
)
}
} else {
setState {
AccelerometerHistoryContract.State.Display(
loadingHistoryState = ProgressState.Indeterminate,
previousHistory = null
)
}
}
job?.cancel()
job = getAccelerometerSpectreBySerial(
serial = event.serial,
accelMode = event.accelMode,
fftAxis = event.fftAxis,
fftMode = event.fftMode,
frequency = event.frequency,
accelScale = event.accelScale
).onEach {
val currentState = viewState.value
if(currentState is AccelerometerHistoryContract.State.Display) {
it.fold(
onSuccess = {
setState {
AccelerometerHistoryContract.State.Display(
loadingHistoryState = it,
previousHistory = when (it) {
is ProgressState.Finished -> {
it.data
}
is ProgressState.Indeterminate -> currentState.previousHistory
is ProgressState.Progress -> currentState.previousHistory
}
)
}
},
onFailure = {
setState {
AccelerometerHistoryContract.State.Exception
}
}
)
}
}.launchIn(this)
}
}
private fun reduce(
state: AccelerometerHistoryContract.State,
event: AccelerometerHistoryContract.Event.OnRefreshHistory
) {
/*viewModelScope.launch {
setState { setState {
AccelerometerHistoryContract.State.Display(ProgressState.Indeterminate) AccelerometerHistoryContract.State.Display(ProgressState.Indeterminate)
} }
getAccelerometerSpectreBySerial( measureJob?.cancel()
serial = event.serial, measureJob = null
accelMode = event.accelMode,
fftAxis = event.fftAxis, measureJob = getAccelerometerHistoryBySerial(event.serial).onEach {
fftMode = event.fftMode,
frequency = event.frequency,
accelScale = event.accelScale
).onEach {
it.fold( it.fold(
onSuccess = { onSuccess = {
setState { setState {
@ -489,14 +378,43 @@ class AccelerometerHistoryViewModel @Inject constructor(
) )
}.launchIn(this) }.launchIn(this)
}*/ }
}
}
} }
private fun reduce( private fun reduce(
state: AccelerometerHistoryContract.State, state: AccelerometerHistoryContract.State,
event: AccelerometerHistoryContract.Event.StopMeasure event: AccelerometerHistoryContract.Event.OnRefreshHistory
) { ) {
job?.cancel() viewModelScope.launch {
setState {
AccelerometerHistoryContract.State.Display(ProgressState.Indeterminate)
}
measureJob?.cancel()
measureJob = null
measureJob = getAccelerometerHistoryBySerial(event.serial).onEach {
it.fold(
onSuccess = {
setState {
AccelerometerHistoryContract.State.Display(it)
}
},
onFailure = {
setState {
AccelerometerHistoryContract.State.Exception
}
}
)
}.launchIn(this)
}
} }
} }

View File

@ -18,6 +18,7 @@ import androidx.compose.ui.unit.dp
import llc.arma.ble.app.ui.model.BleView import llc.arma.ble.app.ui.model.BleView
import llc.arma.ble.app.ui.screen.BleInfoView import llc.arma.ble.app.ui.screen.BleInfoView
import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerContract import llc.arma.ble.app.ui.screen.inspection.accelerometer.AccelerometerContract
import llc.arma.ble.app.ui.screen.inspection.thermometer.ThermometerContract
import llc.arma.ble.domain.model.Ble import llc.arma.ble.domain.model.Ble
@Composable @Composable
@ -89,6 +90,123 @@ fun DisplayState(
} }
Box(
modifier = Modifier.padding(
vertical = 8.dp,
horizontal = 8.dp
)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
.clickable { }
.padding(8.dp)
) {
Column(
modifier = Modifier.weight(1f)
) {
Text(
text = "Сохранять историю измерений"
)
}
Switch(
checked = ble.accelerometerState.saveHistory,
onCheckedChange = {
onEvent(AccelerometerContract.Event.OnSaveHistoryChanged(it))
}
)
}
}
Box(
modifier = Modifier.padding(
vertical = 8.dp,
horizontal = 8.dp
)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
.clickable {
onEvent(AccelerometerContract.Event.OnSaveIntervalEdit)
}
.padding(8.dp)
) {
Column(
modifier = Modifier.weight(1f)
) {
Text(
text = "Интервал измерений"
)
val hours = ble.accelerometerState.historyInterval / 1000 / 60 / 60
val minutes = (ble.accelerometerState.historyInterval - ( hours * 1000 * 60 * 60 )) / 1000 / 60
Text(
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.bodyMedium,
text = "$hours ч. $minutes мин."
)
}
Icon(
imageVector = Icons.Rounded.KeyboardArrowDown,
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 = "График измерений"
)
}
Icon(
imageVector = Icons.Rounded.KeyboardArrowRight,
contentDescription = null
)
}
}
Box( Box(
modifier = Modifier.padding( modifier = Modifier.padding(
vertical = 8.dp, vertical = 8.dp,
@ -159,6 +277,42 @@ fun DisplayState(
} }
Box(
modifier = Modifier.padding(
vertical = 8.dp,
horizontal = 8.dp
)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.clip(RoundedCornerShape(16.dp))
.clickable {
onEvent(AccelerometerContract.Event.OnChangePassword)
}
.padding(8.dp)
) {
Column(
modifier = Modifier.weight(1f)
) {
Text(
text = "Изменить пароль"
)
}
Icon(
imageVector = Icons.Rounded.KeyboardArrowRight,
contentDescription = null
)
}
}
} }
) )

View File

@ -0,0 +1,202 @@
package llc.arma.ble.app.ui.screen.inspection.accelerometer.view
import androidx.compose.animation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.KeyboardArrowDown
import androidx.compose.material.icons.rounded.KeyboardArrowUp
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 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 IntervalEdit(
state: BleView.Accelerometer,
onEvent: (AccelerometerContract.Event) -> Unit,
){
var value by remember(state.accelerometerState.historyInterval) {
mutableStateOf((state.accelerometerState.historyInterval / 1000 / 60 / 60).toInt())
}
val maxInterval = 240
if(value > maxInterval){
value = maxInterval
}
if(value < 1){
value = 1
}
val maxHours = maxInterval
val maxDays = maxInterval / 24
val dayValue = value / 24
val hourValue = value - (24 * dayValue)
Column(
modifier = Modifier
) {
Text(
modifier = Modifier.padding(horizontal = 12.dp),
text = "Интервал измерений",
style = MaterialTheme.typography.titleLarge
)
Spacer(modifier = Modifier.height(16.dp))
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.align(Alignment.CenterHorizontally)
) {
NumberPicker(
range = -1..maxDays,
value = dayValue,
onValueChanged = { value = (it * 24) + hourValue }
)
Spacer(modifier = Modifier.width(8.dp))
Text(text = "Дни")
Spacer(modifier = Modifier.width(16.dp))
NumberPicker(
range = -1..maxHours,
value = hourValue,
onValueChanged = {
value = it + (dayValue * 24)
}
)
Spacer(modifier = Modifier.width(8.dp))
Text(text = "Часы")
}
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.OnSaveIntervalChanged(
value.toLong() * 1000 * 60 * 60
)
)
}
) {
Box(modifier = Modifier.fillMaxSize()) {
Text(
modifier = Modifier.align(Alignment.Center),
color = MaterialTheme.colorScheme.background,
style = MaterialTheme.typography.labelLarge,
text = "Применить"
)
}
}
}
}
@Composable
fun NumberPicker(
modifier: Modifier = Modifier,
range: IntRange,
value: Int,
onValueChanged: (Int) -> Unit
) {
LaunchedEffect(range){
if(value > range.last){
onValueChanged(range.last)
}
if(value < range.first){
onValueChanged(range.first)
}
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
){
FilledIconButton(
onClick = {
if(value < range.last) onValueChanged(value + 1)
}
) {
Icon(
imageVector = Icons.Rounded.KeyboardArrowUp,
contentDescription = null
)
}
Spacer(modifier = Modifier.height(36.dp))
AnimatedContent(
targetState = value,
transitionSpec = {
if (targetState > initialState) {
(slideInVertically { height -> height } + fadeIn()).togetherWith(
slideOutVertically { height -> -height } + fadeOut())
} else {
(slideInVertically { height -> -height } + fadeIn()).togetherWith(
slideOutVertically { height -> height } + fadeOut())
}.using(
SizeTransform(clip = false)
)
}
) { targetCount ->
Text(
style = MaterialTheme.typography.displaySmall,
text = "$targetCount"
)
}
Spacer(modifier = Modifier.height(36.dp))
FilledIconButton(
onClick = {
if(value > range.first) onValueChanged(value - 1)
}
) {
Icon(
imageVector = Icons.Rounded.KeyboardArrowDown,
contentDescription = null
)
}
}
}

View File

@ -44,7 +44,7 @@ fun Write(
when (state) { when (state) {
is AccelerometerContract.State.Display.WriteState.DisplayPreview -> { is AccelerometerContract.State.Display.WriteState.DisplayPreview -> {
if(state.writeRequest.tx != null) { if(state.writeRequest.tx != null || state.writeRequest.saveHistory != null || state.writeRequest.historyInterval != null) {
state.writeRequest.tx?.let { state.writeRequest.tx?.let {
Box( Box(
@ -81,6 +81,80 @@ fun Write(
} }
} }
state.writeRequest.saveHistory?.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
)
}
}
}
}
state.writeRequest.historyInterval?.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 / 1000 / 60 / 60} ч."
)
}
}
}
}
Spacer(modifier = Modifier.height(20.dp)) Spacer(modifier = Modifier.height(20.dp))
Surface( Surface(

View File

@ -204,7 +204,7 @@ fun Display(
animationSpec = tween( animationSpec = tween(
durationMillis = progressAnimDuration, durationMillis = progressAnimDuration,
easing = FastOutSlowInEasing easing = FastOutSlowInEasing
) ), label = ""
) )
CircularProgressIndicator( CircularProgressIndicator(
@ -221,9 +221,7 @@ fun Display(
} }
@Composable @Composable
private fun Exception( private fun Exception() {
) {
Box( Box(
modifier = Modifier modifier = Modifier
.padding(8.dp) .padding(8.dp)

View File

@ -106,7 +106,7 @@ fun Write(
Text( Text(
color = MaterialTheme.colorScheme.secondary, color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
text = "${it.localizedName}" text = it.localizedName
) )
} }

View File

@ -126,6 +126,11 @@ class BleRepositoryImpl @Inject constructor(
private val app: Application private val app: Application
) : BleRepository { ) : BleRepository {
private val ScanResult.timerEnabled: Boolean
get() {
return scanRecord?.manufacturerSpecificData?.get(89)?.get(2) == 1.toByte()
}
private val ScanResult.info: BleInfo private val ScanResult.info: BleInfo
get() { get() {
return BleInfo( return BleInfo(
@ -135,15 +140,11 @@ class BleRepositoryImpl @Inject constructor(
rssi = rssi, rssi = rssi,
type = type, type = type,
scanTime = timestampNanos / 1_000_000, scanTime = timestampNanos / 1_000_000,
tx = scanRecord?.txPowerLevel ?: 0 tx = scanRecord?.txPowerLevel ?: 0,
recordEnabled = timerEnabled
) )
} }
private val ScanResult.timerEnabled: Boolean
get() {
return scanRecord?.manufacturerSpecificData?.get(89)?.get(2) == 1.toByte()
}
private val ScanResult.batteryLevel: Int? private val ScanResult.batteryLevel: Int?
get() { get() {
return scanRecord?.manufacturerSpecificData?.get(89)?.get(1) return scanRecord?.manufacturerSpecificData?.get(89)?.get(1)
@ -300,6 +301,24 @@ class BleRepositoryImpl @Inject constructor(
return when(result.info.type) { return when(result.info.type) {
BleInfo.Type.ACCELEROMETER -> { BleInfo.Type.ACCELEROMETER -> {
val tState = suspendCancellableCoroutine {
CoroutineScope(Dispatchers.IO).launch {
it.resume(readAccelState(result))
}
}.fold(
onFailure = {
return Result.failure(it)
},
onSuccess = {
it
}
)
Result.success( Result.success(
flow { flow {
@ -329,7 +348,8 @@ class BleRepositoryImpl @Inject constructor(
4 -> Ble.BleState.TX.PLUS_4 4 -> Ble.BleState.TX.PLUS_4
else -> Ble.BleState.TX.ZERO else -> Ble.BleState.TX.ZERO
} }
) ),
accelerometerState = tState
) )
) )
@ -457,10 +477,6 @@ class BleRepositoryImpl @Inject constructor(
) )
} }
BleInfo.Type.ACCELEROMETER -> {
TODO()
}
} }
} }
@ -497,6 +513,26 @@ class BleRepositoryImpl @Inject constructor(
} }
private suspend fun readAccelState(
record: ScanResult
): Result<Ble.Accelerometer.AccelerometerState, BleException> {
val history = readHistoryInterval(record).fold(
onFailure = {
return Result.failure(it)
},
onSuccess = { return@fold it }
)
return Result.success(
Ble.Accelerometer.AccelerometerState(
saveHistory = record.timerEnabled,
historyInterval = history
)
)
}
@OptIn(ExperimentalUnsignedTypes::class) @OptIn(ExperimentalUnsignedTypes::class)
private suspend fun readTemperature( private suspend fun readTemperature(
record: ScanResult record: ScanResult
@ -620,7 +656,45 @@ class BleRepositoryImpl @Inject constructor(
if (checkPermission()) { if (checkPermission()) {
gatt = it.connectGatt(app, false, ReadHistoryCallback(app) { gatt = it.connectGatt(app, false, ReadTemperatureHistoryCallback(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 getAccelerometerHistoryBySerial(
serial: String
): Flow<Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>> {
var gatt: BluetoothGatt? = null
return callbackFlow {
deviceCache[serial]?.device?.let {
if (checkPermission()) {
gatt = it.connectGatt(app, false, ReadAccelerometerHistoryCallback(app) {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
send(it) send(it)
} }

View File

@ -165,7 +165,7 @@ class ReadAccelerometerCallback(
Log.d("accel", "notification") Log.d("accel", "notification")
val data = value.toList().chunked(2).map { val data = value.toList().chunked(2).map {
it.toByteArray().get2byteShortAt(0) it.toByteArray().get2byteShortAt()
} }
onResult( onResult(

View File

@ -0,0 +1,337 @@
package llc.arma.ble.data
import android.Manifest
import android.app.Application
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import androidx.core.app.ActivityCompat
import llc.arma.ble.domain.Result
import llc.arma.ble.domain.common.BleException
import llc.arma.ble.domain.common.ProgressState
import llc.arma.ble.domain.model.Ble
class ReadAccelerometerHistoryCallback(
private val app: Application,
private val onResult: (Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>) -> Unit
) : BluetoothGattCallback() {
enum class Property {
DATA_SIZE, PACKAGE
}
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)
if(status == BluetoothGatt.GATT_SUCCESS){
gatt.getService(serviceUUID)?.getCharacteristic(accelerometerHistoryReadUUID)?.let {
if (checkPermission()) {
readProperty = Property.DATA_SIZE
gatt.writeCharacteristic(it, byteArrayOf(2))
} else {
onResult(Result.failure(BleException.PermissionDenied))
gatt.close()
}
}
}
}
private var lastMeasureSystemTime: Long? = null
private var bleMeasureInterval: Long? = null
private var bleRealTime: Long? = null
private var bleLastMeasureTime: Long? = null
private val resultTemperaturePackage: MutableList<Float> = mutableListOf()
var expectedDataSize: Int? = null
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
){
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 = System.currentTimeMillis() - ((bleRealTime!! - bleLastMeasureTime!!) * 1_000)
val temperatureDataArray = value.toUByteArray().asList().subList(16, value.size)
resultTemperaturePackage.addAll(
temperatureDataArray.chunked(2).map {
it.toUByteArray().toByteArray().get2byteShortAt().toFloat()
}.toMutableList()
)
val nextPackageDataCount = value.get2byteUIntAt(2)
expectedDataSize = nextPackageDataCount.toInt() + resultTemperaturePackage.size
onResult(Result.success(ProgressState.Progress(0f / expectedDataSize!!.toFloat())))
onResult(Result.success(ProgressState.Progress(resultTemperaturePackage.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(
resultTemperaturePackage.withIndex().map {
Ble.Accelerometer.MeasurePoint(
frequency = lastMeasureSystemTime!! - (((resultTemperaturePackage.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)
resultTemperaturePackage.addAll(
temperatureDataArray.chunked(2).map {
it.toUByteArray().toByteArray().get2byteShortAt().toFloat()
}
)
onResult(Result.success(ProgressState.Progress(resultTemperaturePackage.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(
resultTemperaturePackage.withIndex().map {
Ble.Accelerometer.MeasurePoint(
frequency = lastMeasureSystemTime!! - (((resultTemperaturePackage.size - 1) - it.index) * bleMeasureInterval!!),
value = it.value
)
}
)
)
)
gatt.close()
}
} else {
onResult(Result.failure(BleException.UnexpectedResponse))
gatt.close()
}
}
}
else -> {
onResult(Result.failure(BleException.UnexpectedResponse))
gatt.close()
}
}
}
}
override fun onCharacteristicWrite(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
super.onCharacteristicWrite(gatt, characteristic, status)
if(status == BluetoothGatt.GATT_SUCCESS){
if (checkPermission()) {
gatt.readCharacteristic(characteristic)
} else {
onResult(Result.failure(BleException.PermissionDenied))
gatt.close()
}
} else {
onResult(Result.failure(BleException.UnexpectedResponse))
gatt.close()
}
}
fun checkPermission(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_CONNECT) ==
PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_SCAN) ==
PackageManager.PERMISSION_GRANTED
} else {
return ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_COARSE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
}
}
fun BluetoothGatt.writeCharacteristic(
characteristic: BluetoothGattCharacteristic,
data: ByteArray
): Result<Unit, BleException>{
return if(checkPermission()){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
writeCharacteristic(characteristic, data, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
}else{
characteristic.value = data
writeCharacteristic(characteristic)
}
Result.success(Unit)
} else {
Result.failure(BleException.PermissionDenied)
}
}
}

View File

@ -23,13 +23,12 @@ import java.nio.ByteBuffer
import java.nio.ByteOrder.LITTLE_ENDIAN import java.nio.ByteOrder.LITTLE_ENDIAN
import java.util.UUID import java.util.UUID
fun ByteArray.get2byteShortAt(idx: Int): Int { fun ByteArray.get2byteShortAt(): Int {
val shorts = ShortArray(1) val shorts = ShortArray(1)
ByteBuffer.wrap(this).order(LITTLE_ENDIAN).asShortBuffer()[shorts] ByteBuffer.wrap(this).order(LITTLE_ENDIAN).asShortBuffer()[shorts]
return shorts[0].toInt()//(this[0].toInt() + (this[1].toInt() shl 8)).toShort() return shorts[0].toInt()
} }
class ReadAccelerometerSpectreCallback( class ReadAccelerometerSpectreCallback(
private val app: Application, private val app: Application,
private val accelScale: AccelScale, private val accelScale: AccelScale,
@ -67,11 +66,6 @@ class ReadAccelerometerSpectreCallback(
) { ) {
super.onConnectionStateChange(gatt, status, newState) super.onConnectionStateChange(gatt, status, newState)
Log.d("spectre", "onConnectionStateChange")
if(status == BluetoothGatt.GATT_SUCCESS){ if(status == BluetoothGatt.GATT_SUCCESS){
if(newState == BluetoothGatt.STATE_CONNECTED){ if(newState == BluetoothGatt.STATE_CONNECTED){
@ -98,10 +92,9 @@ class ReadAccelerometerSpectreCallback(
status: Int status: Int
) { ) {
super.onServicesDiscovered(gatt, status) super.onServicesDiscovered(gatt, status)
Log.d("spectre", "onServicesDiscovered")
if(status == BluetoothGatt.GATT_SUCCESS){ if(status == BluetoothGatt.GATT_SUCCESS){
enableNotifications(gatt) enableNotifications(gatt)
} }
} }
@ -148,7 +141,7 @@ class ReadAccelerometerSpectreCallback(
private val resultAccelerometerPackage: MutableList<Float> = mutableListOf() private val resultAccelerometerPackage: MutableList<Float> = mutableListOf()
var expectedDataSize: Int? = null private var expectedDataSize: Int? = null
@Deprecated("Deprecated in Java") @Deprecated("Deprecated in Java")
override fun onCharacteristicRead( override fun onCharacteristicRead(
@ -189,7 +182,7 @@ class ReadAccelerometerSpectreCallback(
){ ){
if(characteristic.uuid == accelerometerReadUUID) { if(characteristic.uuid == accelerometerReadUUID) {
Log.d("spectre", "changed")
readProperty = Property.DATA_SIZE readProperty = Property.DATA_SIZE
gatt.getService(serviceUUID).getCharacteristic(accelerometerHistoryReadUUID)?.let { gatt.getService(serviceUUID).getCharacteristic(accelerometerHistoryReadUUID)?.let {
gatt.writeCharacteristic(it, byteArrayOf(2)) gatt.writeCharacteristic(it, byteArrayOf(2))
@ -215,8 +208,6 @@ class ReadAccelerometerSpectreCallback(
status: Int status: Int
){ ){
Log.d("spectre", "onCharacteristicRead")
if(status == BluetoothGatt.GATT_SUCCESS){ if(status == BluetoothGatt.GATT_SUCCESS){
when(readProperty){ when(readProperty){
Property.DATA_SIZE -> { Property.DATA_SIZE -> {
@ -257,7 +248,7 @@ class ReadAccelerometerSpectreCallback(
resultAccelerometerPackage.addAll( resultAccelerometerPackage.addAll(
accelerometerDataArray.chunked(2).map { accelerometerDataArray.chunked(2).map {
it.toByteArray().get2byteShortAt(0).toFloat() it.toByteArray().get2byteShortAt().toFloat()
}.toMutableList() }.toMutableList()
) )
@ -309,7 +300,7 @@ class ReadAccelerometerSpectreCallback(
resultAccelerometerPackage.addAll( resultAccelerometerPackage.addAll(
temperatureDataArray.chunked(2).map { temperatureDataArray.chunked(2).map {
it.toByteArray().get2byteShortAt(0).toFloat() it.toByteArray().get2byteShortAt().toFloat()
} }
) )
@ -365,8 +356,6 @@ class ReadAccelerometerSpectreCallback(
) { ) {
super.onCharacteristicWrite(gatt, characteristic, status) super.onCharacteristicWrite(gatt, characteristic, status)
Log.d("spectre", "request written $readProperty")
if(readProperty !== null) { if(readProperty !== null) {
if (status == BluetoothGatt.GATT_SUCCESS) { if (status == BluetoothGatt.GATT_SUCCESS) {
@ -390,29 +379,19 @@ class ReadAccelerometerSpectreCallback(
} }
override fun onMtuChanged(gatt: BluetoothGatt?, mtu: Int, status: Int) {
super.onMtuChanged(gatt, mtu, status)
Log.d("spectre", "mtu $mtu")
}
override fun onDescriptorWrite( override fun onDescriptorWrite(
gatt: BluetoothGatt, gatt: BluetoothGatt,
descriptor: BluetoothGattDescriptor, descriptor: BluetoothGattDescriptor,
status: Int status: Int
) { ) {
Log.d("spectre", "descriptor written")
super.onDescriptorWrite(gatt, descriptor, status) super.onDescriptorWrite(gatt, descriptor, status)
start(gatt) start(gatt)
} }
private fun start( private fun start(
gatt: BluetoothGatt, gatt: BluetoothGatt,
){ ){
Log.d("spectre", "start")
gatt.getService(serviceUUID)?.getCharacteristic(accelerometerReadUUID)?.let { gatt.getService(serviceUUID)?.getCharacteristic(accelerometerReadUUID)?.let {
if (checkPermission()) { if (checkPermission()) {

View File

@ -13,17 +13,16 @@ import llc.arma.ble.domain.Result
import llc.arma.ble.domain.common.BleException import llc.arma.ble.domain.common.BleException
import llc.arma.ble.domain.common.ProgressState import llc.arma.ble.domain.common.ProgressState
import llc.arma.ble.domain.model.Ble import llc.arma.ble.domain.model.Ble
import java.util.stream.Collectors
enum class Property { class ReadTemperatureHistoryCallback(
DATA_SIZE, PACKAGE
}
class ReadHistoryCallback(
private val app: Application, private val app: Application,
private val onResult: (Result<ProgressState<List<Ble.Thermometer.MeasurePoint>>, BleException>) -> Unit private val onResult: (Result<ProgressState<List<Ble.Thermometer.MeasurePoint>>, BleException>) -> Unit
) : BluetoothGattCallback() { ) : BluetoothGattCallback() {
enum class Property {
DATA_SIZE, PACKAGE
}
private fun ByteArray.get4byteUIntAt(idx: Int) = private fun ByteArray.get4byteUIntAt(idx: Int) =
((this[idx + 3].toUInt() and 0xFFu) shl 24) or ((this[idx + 3].toUInt() and 0xFFu) shl 24) or
((this[idx + 2].toUInt() and 0xFFu) shl 16) or ((this[idx + 2].toUInt() and 0xFFu) shl 16) or

View File

@ -64,10 +64,43 @@ class WriteAccelerometerCallback(
status: Int status: Int
){ ){
if(request.tx != null) { if(request.tx != null || request.saveHistory != null) {
fun UInt.to4ByteArrayInLittleEndian(): ByteArray =
(3 downTo 0).map {
(this shr (it * Byte.SIZE_BITS)).toByte()
}.toByteArray()
var uuid: Pair<UUID, ByteArray>? = null var uuid: Pair<UUID, ByteArray>? = null
uuid = request.historyInterval?.let {
this.request = request.copy(
historyInterval = null
)
Pair(
intervalWriteUUID,
mutableListOf<Byte>(3).apply {
addAll((it).toUInt().to4ByteArrayInLittleEndian().reversed().toList())
}.toByteArray()
)
}
uuid = request.saveHistory?.let {
this.request = request.copy(
saveHistory = null
)
Pair(
saveEnabledWriteUUID,
mutableListOf<Byte>(4).apply {
add(if (it) 1 else 0)
}.toByteArray()
)
} ?: uuid
uuid = request.tx?.let { uuid = request.tx?.let {
this.request = request.copy( this.request = request.copy(
@ -143,8 +176,6 @@ class WriteAccelerometerCallback(
status: Int status: Int
) { ) {
Log.d("beacon", "onCharacteristicWrite $status")
super.onCharacteristicWrite(gatt, characteristic, status) super.onCharacteristicWrite(gatt, characteristic, status)
if(checkPermission()) { if(checkPermission()) {

View File

@ -34,8 +34,6 @@ class WriteThermometerCallback(
) { ) {
super.onConnectionStateChange(gatt, status, newState) super.onConnectionStateChange(gatt, status, newState)
Log.d("th", "onConnectionStateChange $status $newState")
if(checkPermission()) { if(checkPermission()) {
if(status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) { if(status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
@ -60,7 +58,6 @@ class WriteThermometerCallback(
gatt: BluetoothGatt, gatt: BluetoothGatt,
status: Int status: Int
) { ) {
Log.d("th", "onServicesDiscovered $status")
super.onServicesDiscovered(gatt, status) super.onServicesDiscovered(gatt, status)
onCycle(gatt, status) onCycle(gatt, status)
@ -183,8 +180,6 @@ class WriteThermometerCallback(
status: Int status: Int
) { ) {
Log.d("th", "onCharacteristicWrite $status")
super.onCharacteristicWrite(gatt, characteristic, status) super.onCharacteristicWrite(gatt, characteristic, status)
if(checkPermission()) { if(checkPermission()) {

View File

@ -6,11 +6,14 @@ sealed class Ble(
class Accelerometer( class Accelerometer(
info: BleInfo, info: BleInfo,
val state: BleState val state: BleState,
val accelerometerState: AccelerometerState
): Ble(info) { ): Ble(info) {
data class WriteRequest( data class WriteRequest(
val tx: BleState.TX?, val tx: BleState.TX?,
val saveHistory: Boolean?,
val historyInterval: Long?
) )
class MeasurePoint ( class MeasurePoint (
@ -18,6 +21,11 @@ sealed class Ble(
val value: Float val value: Float
) )
data class AccelerometerState(
val saveHistory: Boolean,
val historyInterval: Long
)
} }
class Beacon( class Beacon(

View File

@ -7,7 +7,8 @@ data class BleInfo(
val rssi: Int?, val rssi: Int?,
val type: Type, val type: Type,
val scanTime: Long, val scanTime: Long,
val tx: Int val tx: Int,
val recordEnabled: Boolean
){ ){
enum class Type { enum class Type {

View File

@ -50,4 +50,6 @@ interface BleRepository {
frequency: FftFrequency frequency: FftFrequency
): Flow<Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>> ): Flow<Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>>
suspend fun getAccelerometerHistoryBySerial(serial: String): Flow<Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>>
} }

View File

@ -0,0 +1,21 @@
package llc.arma.ble.domain.usecase
import kotlinx.coroutines.flow.Flow
import llc.arma.ble.domain.Result
import llc.arma.ble.domain.common.BleException
import llc.arma.ble.domain.common.ProgressState
import llc.arma.ble.domain.model.Ble
import llc.arma.ble.domain.repository.BleRepository
import javax.inject.Inject
class GetAccelerometerHistoryBySerial @Inject constructor(
private val bleRepository: BleRepository
) {
suspend operator fun invoke(serial: String): Flow<Result<ProgressState<List<Ble.Accelerometer.MeasurePoint>>, BleException>> {
return bleRepository.getAccelerometerHistoryBySerial(serial)
}
}