Fix some bugs, improve ui
This commit is contained in:
parent
5e309d001c
commit
2a43916ecc
|
|
@ -76,8 +76,6 @@ dependencies {
|
|||
|
||||
implementation "com.google.accompanist:accompanist-permissions:0.26.3-beta"
|
||||
|
||||
implementation "com.chargemap.compose:numberpicker:1.0.3"
|
||||
|
||||
implementation "com.patrykandpatrick.vico:core:1.6.4"
|
||||
implementation "com.patrykandpatrick.vico:compose:1.6.4"
|
||||
implementation "com.patrykandpatrick.vico:compose-m3:1.6.4"
|
||||
|
|
|
|||
|
|
@ -81,10 +81,23 @@ fun BleInfoView(
|
|||
contentDescription = null
|
||||
)
|
||||
},
|
||||
title = "Заряд аккумулятора",
|
||||
title = "Заряд батареи",
|
||||
subtitle = "${bleInfo.batteryLevel} %"
|
||||
)
|
||||
|
||||
SpecDivider()
|
||||
|
||||
BleInfoItem(
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.NetworkCell,
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
title = "Мощность сигнала",
|
||||
subtitle = if(bleInfo.rssi != null) "${bleInfo.rssi } dBm" else "Нет сигнала"
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import llc.arma.ble.app.ui.common.ViewState
|
|||
import llc.arma.ble.app.ui.model.BleView
|
||||
import llc.arma.ble.app.ui.screen.beacon.BeaconContract
|
||||
import llc.arma.ble.app.ui.screen.thermometer.ThermometerContract
|
||||
import llc.arma.ble.domain.common.BleException
|
||||
import llc.arma.ble.domain.model.Ble
|
||||
import llc.arma.ble.domain.usecase.GetBleBySerial
|
||||
|
||||
|
|
@ -32,7 +33,7 @@ class ConnectionContract {
|
|||
object Loading : State()
|
||||
|
||||
data class DisplayException(
|
||||
val exception: GetBleBySerial.GetBleException
|
||||
val exception: BleException
|
||||
) : State()
|
||||
|
||||
data class Display(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package llc.arma.ble.app.ui.screen.connection
|
|||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import llc.arma.ble.app.ui.common.BaseViewModel
|
||||
import llc.arma.ble.app.ui.mapper.BleMapper
|
||||
|
|
@ -106,11 +108,13 @@ class ConnectionViewModel @Inject constructor(
|
|||
getBleBySerial(serial).fold(
|
||||
onSuccess = {
|
||||
|
||||
setState {
|
||||
ConnectionContract.State.Display(
|
||||
ble = it
|
||||
)
|
||||
}
|
||||
it.onEach {
|
||||
setState {
|
||||
ConnectionContract.State.Display(
|
||||
ble = it
|
||||
)
|
||||
}
|
||||
}.launchIn(viewModelScope)
|
||||
|
||||
},
|
||||
onFailure = {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import androidx.compose.material3.*
|
|||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
|
|
@ -27,6 +28,7 @@ fun Loading(
|
|||
) {
|
||||
|
||||
CircularProgressIndicator(
|
||||
strokeCap = StrokeCap.Round,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -42,8 +42,6 @@ class ThermometerContract {
|
|||
val ble: Ble.Thermometer
|
||||
) : Event()
|
||||
|
||||
data class OnTxChanged(val tx: Int) : Event()
|
||||
|
||||
object OnNavigateUpClicked : Event()
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -201,6 +201,7 @@ fun ThermometerScreen(
|
|||
when(state){
|
||||
is ThermometerContract.State.Display -> {
|
||||
DisplayState(
|
||||
origin = state.origin,
|
||||
ble = state.thermometer,
|
||||
onEvent = {
|
||||
viewModel.setEvent(it)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ class ThermometerViewModel @Inject constructor(
|
|||
override fun handleEvents(event: ThermometerContract.Event) {
|
||||
when(event){
|
||||
is ThermometerContract.Event.OnNavigateUpClicked -> reduce(viewState.value, event)
|
||||
is ThermometerContract.Event.OnTxChanged -> reduce(viewState.value, event)
|
||||
is ThermometerContract.Event.OnBleChanged -> reduce(viewState.value, event)
|
||||
is ThermometerContract.Event.OnSaveIntervalChanged -> reduce(viewState.value, event)
|
||||
is ThermometerContract.Event.OnSaveIntervalEdit -> reduce(viewState.value, event)
|
||||
|
|
@ -46,24 +45,30 @@ class ThermometerViewModel @Inject constructor(
|
|||
setEffect { ThermometerContract.Effect.Navigation.NavigateUp }
|
||||
}
|
||||
|
||||
private fun reduce(
|
||||
state: ThermometerContract.State,
|
||||
event: ThermometerContract.Event.OnTxChanged
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
private fun reduce(
|
||||
state: ThermometerContract.State,
|
||||
event: ThermometerContract.Event.OnBleChanged
|
||||
) {
|
||||
setState {
|
||||
ThermometerContract.State.Display(
|
||||
origin = event.ble,
|
||||
thermometer = bleMapper.map(event.ble) as BleView.Thermometer,
|
||||
writeState = null
|
||||
)
|
||||
|
||||
when(state){
|
||||
is ThermometerContract.State.Display -> setState {
|
||||
state.copy(
|
||||
origin = Ble.Thermometer(
|
||||
info = event.ble.info,
|
||||
state = state.origin.state,
|
||||
thermometerState = state.origin.thermometerState
|
||||
)
|
||||
)
|
||||
}
|
||||
is ThermometerContract.State.Loading -> setState {
|
||||
ThermometerContract.State.Display(
|
||||
origin = event.ble,
|
||||
thermometer = bleMapper.map(event.ble) as BleView.Thermometer,
|
||||
writeState = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun reduce(
|
||||
|
|
@ -188,25 +193,48 @@ class ThermometerViewModel @Inject constructor(
|
|||
|
||||
if(state is ThermometerContract.State.Display){
|
||||
|
||||
state.writeState?.let {
|
||||
state.writeState?.let { request ->
|
||||
|
||||
if(it is ThermometerContract.State.Display.WriteState.DisplayPreview) {
|
||||
if(request is ThermometerContract.State.Display.WriteState.DisplayPreview) {
|
||||
|
||||
viewModelScope.launch {
|
||||
|
||||
setState {
|
||||
state.copy(
|
||||
writeState = ThermometerContract.State.Display.WriteState.Writing(it.writeRequest)
|
||||
writeState = ThermometerContract.State.Display.WriteState.Writing(request.writeRequest)
|
||||
)
|
||||
}
|
||||
|
||||
writeBle(state.thermometer.info.serial, it.writeRequest).fold(
|
||||
writeBle(state.thermometer.info.serial, request.writeRequest).fold(
|
||||
onSuccess = {
|
||||
setState {
|
||||
state.copy(
|
||||
writeState = ThermometerContract.State.Display.WriteState.Success
|
||||
|
||||
val currentState = viewState.value
|
||||
|
||||
if(currentState is ThermometerContract.State.Display) {
|
||||
|
||||
val newBleObject = Ble.Thermometer(
|
||||
info = currentState.origin.info,
|
||||
state = currentState.origin.state.copy(
|
||||
tx = request.writeRequest.tx ?: state.origin.state.tx
|
||||
),
|
||||
thermometerState = currentState.origin.thermometerState.copy(
|
||||
saveHistory = request.writeRequest.saveHistory
|
||||
?: currentState.origin.thermometerState.saveHistory,
|
||||
historyInterval = request.writeRequest.historyInterval
|
||||
?: currentState.origin.thermometerState.historyInterval,
|
||||
)
|
||||
)
|
||||
|
||||
setState {
|
||||
currentState.copy(
|
||||
origin = newBleObject,
|
||||
thermometer = bleMapper.map(newBleObject) as BleView.Thermometer,
|
||||
writeState = ThermometerContract.State.Display.WriteState.Success
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
onFailure = {
|
||||
setState {
|
||||
|
|
|
|||
|
|
@ -19,10 +19,12 @@ import androidx.compose.ui.unit.dp
|
|||
import llc.arma.ble.app.ui.model.BleView
|
||||
import llc.arma.ble.app.ui.screen.BleInfoView
|
||||
import llc.arma.ble.app.ui.screen.thermometer.ThermometerContract
|
||||
import llc.arma.ble.domain.model.Ble
|
||||
|
||||
@Composable
|
||||
fun DisplayState(
|
||||
onEvent: (ThermometerContract.Event) -> Unit,
|
||||
origin: Ble.Thermometer,
|
||||
ble: BleView.Thermometer
|
||||
) {
|
||||
|
||||
|
|
@ -40,7 +42,7 @@ fun DisplayState(
|
|||
horizontal = 8.dp
|
||||
)
|
||||
) {
|
||||
BleInfoView(bleInfo = ble.info)
|
||||
BleInfoView(bleInfo = origin.info)
|
||||
}
|
||||
|
||||
Column(
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
package llc.arma.ble.app.ui.screen.thermometer.view
|
||||
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
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 com.chargemap.compose.numberpicker.NumberPicker
|
||||
import llc.arma.ble.app.ui.model.BleView
|
||||
import llc.arma.ble.app.ui.screen.thermometer.ThermometerContract
|
||||
|
||||
|
|
@ -23,6 +24,22 @@ fun IntervalEdit(
|
|||
mutableStateOf((state.thermometerState.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
|
||||
) {
|
||||
|
|
@ -35,28 +52,36 @@ fun IntervalEdit(
|
|||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||
) {
|
||||
|
||||
NumberPicker(
|
||||
dividersColor = MaterialTheme.colorScheme.primary,
|
||||
value = value,
|
||||
onValueChange = {
|
||||
value = it
|
||||
},
|
||||
textStyle = MaterialTheme.typography.titleMedium,
|
||||
range = 1..100
|
||||
range = -1..maxDays,
|
||||
value = dayValue,
|
||||
onValueChanged = { value = (it * 24) + hourValue }
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
||||
Text(
|
||||
text = "ч.",
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
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))
|
||||
|
|
@ -92,4 +117,85 @@ fun IntervalEdit(
|
|||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@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() with
|
||||
slideOutVertically { height -> -height } + fadeOut()
|
||||
} else {
|
||||
slideInVertically { height -> -height } + fadeIn() with
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -278,6 +278,8 @@ class TemperatureHistoryViewModel @Inject constructor(
|
|||
private val getTemperatureHistoryBySerial: GetTemperatureHistoryBySerial
|
||||
) : BaseViewModel<TemperatureHistoryContract.State, TemperatureHistoryContract.Event, TemperatureHistoryContract.Effect>() {
|
||||
|
||||
private var lastSerial: String? = null
|
||||
|
||||
override fun setInitialState() = TemperatureHistoryContract.State.Display(
|
||||
ProgressState.Indeterminate
|
||||
)
|
||||
|
|
@ -297,7 +299,9 @@ class TemperatureHistoryViewModel @Inject constructor(
|
|||
|
||||
if(state is TemperatureHistoryContract.State.Display) {
|
||||
|
||||
if(state.loadingHistoryState is ProgressState.Indeterminate) {
|
||||
if(lastSerial != event.serial) {
|
||||
|
||||
lastSerial = event.serial
|
||||
|
||||
setState {
|
||||
TemperatureHistoryContract.State.Display(ProgressState.Indeterminate)
|
||||
|
|
@ -323,6 +327,7 @@ class TemperatureHistoryViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun reduce(
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -27,10 +28,8 @@ fun Write(
|
|||
onEvent: (ThermometerContract.Event) -> Unit
|
||||
) {
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
Column(
|
||||
modifier = Modifier.animateContentSize { initialValue, targetValue -> }
|
||||
modifier = Modifier.animateContentSize()
|
||||
) {
|
||||
|
||||
Text(
|
||||
|
|
@ -44,69 +43,73 @@ fun Write(
|
|||
when (state) {
|
||||
is ThermometerContract.State.Display.WriteState.DisplayPreview -> {
|
||||
|
||||
state.writeRequest.tx?.let {
|
||||
Box(
|
||||
modifier = Modifier.padding(
|
||||
vertical = 0.dp,
|
||||
horizontal = 8.dp
|
||||
)
|
||||
) {
|
||||
if(state.writeRequest.tx != null || state.writeRequest.saveHistory != null || state.writeRequest.historyInterval != null) {
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.padding(8.dp)
|
||||
state.writeRequest.tx?.let {
|
||||
Box(
|
||||
modifier = Modifier.padding(
|
||||
vertical = 0.dp,
|
||||
horizontal = 8.dp
|
||||
)
|
||||
) {
|
||||
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.padding(8.dp)
|
||||
) {
|
||||
|
||||
Text(
|
||||
text = "Мощность"
|
||||
)
|
||||
Text(
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
text = "${it.localizedName} db"
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
|
||||
Text(
|
||||
text = "Мощность"
|
||||
)
|
||||
Text(
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
text = "${it.localizedName} db"
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
state.writeRequest.saveHistory?.let {
|
||||
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)
|
||||
Box(
|
||||
modifier = Modifier.padding(
|
||||
vertical = 0.dp,
|
||||
horizontal = 8.dp
|
||||
)
|
||||
) {
|
||||
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.padding(8.dp)
|
||||
) {
|
||||
|
||||
Text(
|
||||
text = "Сохранять историю измерений"
|
||||
)
|
||||
Text(
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
text = "${it.localizedName}"
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
|
||||
Text(
|
||||
text = "Сохранять историю измерений"
|
||||
)
|
||||
Text(
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
text = "${it.localizedName}"
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -114,36 +117,36 @@ fun Write(
|
|||
|
||||
}
|
||||
|
||||
}
|
||||
state.writeRequest.historyInterval?.let {
|
||||
|
||||
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)
|
||||
Box(
|
||||
modifier = Modifier.padding(
|
||||
vertical = 0.dp,
|
||||
horizontal = 8.dp
|
||||
)
|
||||
) {
|
||||
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.padding(8.dp)
|
||||
) {
|
||||
|
||||
Text(
|
||||
text = "Интервал измерний"
|
||||
)
|
||||
Text(
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
text = "${it / 1000 / 60 / 60} ч."
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
|
||||
Text(
|
||||
text = "Интервал измерний"
|
||||
)
|
||||
Text(
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
text = "${it / 1000 / 60 / 60} ч."
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -151,55 +154,92 @@ fun Write(
|
|||
|
||||
}
|
||||
|
||||
}
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
Surface(
|
||||
shape = CircleShape,
|
||||
color = MaterialTheme.colorScheme.primaryContainer,
|
||||
onClick = {
|
||||
onEvent(ThermometerContract.Event.OnWriteBle)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp)
|
||||
.height(50.dp),
|
||||
) {
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp)
|
||||
.height(50.dp),
|
||||
shape = CircleShape,
|
||||
color = MaterialTheme.colorScheme.primaryContainer,
|
||||
onClick = {
|
||||
onEvent(ThermometerContract.Event.OnWriteBle)
|
||||
}
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
color = MaterialTheme.colorScheme.background,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
text = "Записать"
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
color = MaterialTheme.colorScheme.background,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
text = "Записать"
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Surface(
|
||||
shape = CircleShape,
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
onClick = {
|
||||
onEvent(ThermometerContract.Event.OnHideWriteBlePreview)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp)
|
||||
.height(50.dp),
|
||||
) {
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
text = "Отменить"
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp)
|
||||
.height(50.dp),
|
||||
shape = CircleShape,
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
onClick = {
|
||||
onEvent(ThermometerContract.Event.OnHideWriteBlePreview)
|
||||
}
|
||||
) {
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
} else {
|
||||
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
text = "Отменить"
|
||||
)
|
||||
Spacer(modifier = Modifier.height(38.dp))
|
||||
|
||||
Text(
|
||||
text = "Нет изменений",
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(64.dp))
|
||||
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp)
|
||||
.height(50.dp),
|
||||
shape = CircleShape,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
onClick = {
|
||||
onEvent(ThermometerContract.Event.OnHideWriteBlePreview)
|
||||
}
|
||||
) {
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
color = MaterialTheme.colorScheme.onPrimary,
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
text = "Ок"
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -216,6 +256,7 @@ fun Write(
|
|||
Spacer(modifier = Modifier.height(28.dp))
|
||||
|
||||
CircularProgressIndicator(
|
||||
strokeCap = StrokeCap.Round,
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import android.bluetooth.le.ScanResult
|
|||
import android.bluetooth.le.ScanSettings
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.SystemClock
|
||||
import android.util.Log
|
||||
import androidx.core.app.ActivityCompat
|
||||
import kotlinx.coroutines.*
|
||||
|
|
@ -183,73 +184,134 @@ class BleRepositoryImpl @Inject constructor(
|
|||
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override suspend fun getBleBySerial(
|
||||
serial: String
|
||||
): Result<Ble, GetBleBySerial.GetBleException> = suspendCancellableCoroutine {
|
||||
): Result<Flow<Ble>, BleException> {
|
||||
|
||||
deviceCache[serial]?.let { result ->
|
||||
|
||||
if (checkPermission()) {
|
||||
return when(result.info.type) {
|
||||
BleInfo.Type.BEACON -> {
|
||||
Result.success(
|
||||
flow {
|
||||
|
||||
if (it.isActive) {
|
||||
while (true) {
|
||||
|
||||
val info = result.info
|
||||
deviceCache[serial]?.let { newResult ->
|
||||
|
||||
val state = Ble.BleState(
|
||||
tx = when (result.scanRecord?.txPowerLevel) {
|
||||
-40 -> Ble.BleState.TX.MINUS_40
|
||||
-20 -> Ble.BleState.TX.MINUS_20
|
||||
-16 -> Ble.BleState.TX.MINUS_16
|
||||
-12 -> Ble.BleState.TX.MINUS_12
|
||||
-8 -> Ble.BleState.TX.MINUS_8
|
||||
-4 -> Ble.BleState.TX.MINUS_4
|
||||
3 -> Ble.BleState.TX.PLUS_3
|
||||
4 -> Ble.BleState.TX.PLUS_4
|
||||
else -> Ble.BleState.TX.ZERO
|
||||
}
|
||||
)
|
||||
val state = Ble.BleState(
|
||||
tx = when (result.scanRecord?.txPowerLevel) {
|
||||
-40 -> Ble.BleState.TX.MINUS_40
|
||||
-20 -> Ble.BleState.TX.MINUS_20
|
||||
-16 -> Ble.BleState.TX.MINUS_16
|
||||
-12 -> Ble.BleState.TX.MINUS_12
|
||||
-8 -> Ble.BleState.TX.MINUS_8
|
||||
-4 -> Ble.BleState.TX.MINUS_4
|
||||
3 -> Ble.BleState.TX.PLUS_3
|
||||
4 -> Ble.BleState.TX.PLUS_4
|
||||
else -> Ble.BleState.TX.ZERO
|
||||
}
|
||||
)
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
emit(
|
||||
|
||||
val resultValue = when (info.type) {
|
||||
Ble.Beacon(
|
||||
info = newResult.info.copy(
|
||||
rssi = if((SystemClock.elapsedRealtimeNanos() - newResult.timestampNanos) > 15_000_000_000) {
|
||||
null
|
||||
} else {
|
||||
newResult.rssi
|
||||
}
|
||||
|
||||
BleInfo.Type.BEACON -> Ble.Beacon(
|
||||
info = info,
|
||||
state = state
|
||||
)
|
||||
),
|
||||
state = state
|
||||
)
|
||||
|
||||
BleInfo.Type.THERMOMETER -> {
|
||||
)
|
||||
|
||||
val thermometer = readThermometerState(result).fold(
|
||||
onFailure = { _ ->
|
||||
return@launch it.resume(Result.failure(GetBleBySerial.GetBleException.BlePermissionDenied))
|
||||
},
|
||||
onSuccess = { return@fold it }
|
||||
)
|
||||
}
|
||||
|
||||
Ble.Thermometer(
|
||||
info = info,
|
||||
state = state,
|
||||
thermometerState = thermometer
|
||||
)
|
||||
delay(500)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
BleInfo.Type.THERMOMETER -> {
|
||||
|
||||
it.resume(Result.success(resultValue)) {}
|
||||
val tState = suspendCancellableCoroutine {
|
||||
|
||||
}
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
|
||||
it.resume(readThermometerState(result))
|
||||
|
||||
}
|
||||
|
||||
}.fold(
|
||||
onFailure = {
|
||||
return Result.failure(it)
|
||||
},
|
||||
onSuccess = {
|
||||
it
|
||||
}
|
||||
)
|
||||
|
||||
Result.success(
|
||||
flow {
|
||||
|
||||
while (true) {
|
||||
|
||||
deviceCache[serial]?.let { newResult ->
|
||||
|
||||
val state = Ble.BleState(
|
||||
tx = when (result.scanRecord?.txPowerLevel) {
|
||||
-40 -> Ble.BleState.TX.MINUS_40
|
||||
-20 -> Ble.BleState.TX.MINUS_20
|
||||
-16 -> Ble.BleState.TX.MINUS_16
|
||||
-12 -> Ble.BleState.TX.MINUS_12
|
||||
-8 -> Ble.BleState.TX.MINUS_8
|
||||
-4 -> Ble.BleState.TX.MINUS_4
|
||||
3 -> Ble.BleState.TX.PLUS_3
|
||||
4 -> Ble.BleState.TX.PLUS_4
|
||||
else -> Ble.BleState.TX.ZERO
|
||||
}
|
||||
)
|
||||
|
||||
emit(
|
||||
|
||||
Ble.Thermometer(
|
||||
info = newResult.info.copy(
|
||||
rssi = if((SystemClock.elapsedRealtimeNanos() - newResult.timestampNanos) > 15_000_000_000) {
|
||||
null
|
||||
} else {
|
||||
newResult.rssi
|
||||
}
|
||||
|
||||
),
|
||||
state = state,
|
||||
thermometerState = tState
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
delay(500)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
it.resume(Result.failure(GetBleBySerial.GetBleException.BlePermissionDenied)) {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return llc.arma.ble.domain.Result.failure(BleException.UnexpectedResponse)
|
||||
|
||||
}
|
||||
|
||||
private suspend fun readThermometerState(
|
||||
|
|
@ -368,38 +430,74 @@ class BleRepositoryImpl @Inject constructor(
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override suspend fun writeBle(
|
||||
serial: String,
|
||||
request: Ble.Thermometer.WriteRequest
|
||||
): Result<Unit, BleException> {
|
||||
): Result<Unit, BleException> = suspendCancellableCoroutine {
|
||||
|
||||
deviceCache[serial]?.let { result ->
|
||||
deviceCache[serial]?.let { scanResult ->
|
||||
|
||||
request.tx?.let { writeTx(result.device, it) }?.onFailure {
|
||||
if(checkPermission()) {
|
||||
|
||||
var gatt: BluetoothGatt? = null
|
||||
|
||||
val callback = WriteThermometerCallback(app, request) { result ->
|
||||
|
||||
gatt?.close()
|
||||
|
||||
result.onSuccess {
|
||||
deviceCache.remove(serial)
|
||||
resultList.remove(serial)
|
||||
}
|
||||
|
||||
it.resume(result)
|
||||
|
||||
}
|
||||
|
||||
gatt = scanResult.device.connectGatt(app, false, callback)
|
||||
|
||||
} else {
|
||||
|
||||
it.resume(Result.failure(BleException.PermissionDenied))
|
||||
|
||||
}
|
||||
|
||||
/*request.tx?.let {
|
||||
Log.d("write", "tx")
|
||||
writeTx(result.device, it)
|
||||
}?.onFailure {
|
||||
Log.d("write", "tx fail")
|
||||
return Result.failure(it)
|
||||
}
|
||||
|
||||
request.historyInterval?.let { writeSaveInterval(result.device, it) }?.onFailure {
|
||||
request.historyInterval?.let {
|
||||
Log.d("write", "in")
|
||||
writeSaveInterval(result.device, it)
|
||||
}?.onFailure {
|
||||
Log.d("write", "in fail")
|
||||
return Result.failure(it)
|
||||
}
|
||||
|
||||
request.saveHistory?.let { writeSaveEnabled(result.device, it) }?.onFailure {
|
||||
request.saveHistory?.let {
|
||||
Log.d("write", "hs")
|
||||
writeSaveEnabled(result.device, it)
|
||||
}?.onFailure {
|
||||
Log.d("write", "hs fail")
|
||||
return Result.failure(it)
|
||||
}
|
||||
|
||||
Log.d("write", "fs")
|
||||
|
||||
writeToFlash(serial).onFailure {
|
||||
Log.d("write", "fs fail")
|
||||
return Result.failure(it)
|
||||
}
|
||||
|
||||
deviceCache.remove(serial)
|
||||
resultList.remove(serial)
|
||||
}*/
|
||||
|
||||
}
|
||||
|
||||
return Result.success(Unit)
|
||||
|
||||
}
|
||||
|
||||
override suspend fun writeBle(
|
||||
|
|
@ -510,7 +608,7 @@ class BleRepositoryImpl @Inject constructor(
|
|||
serviceId = serviceUUID,
|
||||
characteristicId = intervalWriteUUID,
|
||||
writeData = mutableListOf<Byte>(3).apply {
|
||||
addAll(interval.toUInt().to4ByteArrayInBigEndian().toList())
|
||||
addAll((interval / 1_000).toUInt().to4ByteArrayInBigEndian().toList())
|
||||
}.toByteArray()
|
||||
)
|
||||
|
||||
|
|
@ -699,7 +797,9 @@ class BleRepositoryImpl @Inject constructor(
|
|||
if (newState == BluetoothProfile.STATE_CONNECTED) {
|
||||
|
||||
if (checkPermission()) {
|
||||
|
||||
gatt.discoverServices()
|
||||
|
||||
} else {
|
||||
|
||||
it.resume(Result.failure(BleException.PermissionDenied))
|
||||
|
|
@ -708,7 +808,7 @@ class BleRepositoryImpl @Inject constructor(
|
|||
|
||||
} else {
|
||||
|
||||
it.resume(Result.failure(BleException.UnexpectedResponse))
|
||||
it.resume(Result.success(Unit))
|
||||
bleGatt?.close()
|
||||
|
||||
}
|
||||
|
|
@ -752,10 +852,14 @@ class BleRepositoryImpl @Inject constructor(
|
|||
|
||||
}
|
||||
|
||||
Log.d("write", "service not found")
|
||||
|
||||
gatt.disconnect()
|
||||
it.resume(Result.failure(BleException.UnexpectedResponse))
|
||||
|
||||
} else {
|
||||
|
||||
gatt.disconnect()
|
||||
it.resume(Result.failure(BleException.UnexpectedResponse))
|
||||
|
||||
}
|
||||
|
|
@ -787,6 +891,7 @@ class BleRepositoryImpl @Inject constructor(
|
|||
|
||||
} else {
|
||||
|
||||
gatt.close()
|
||||
it.resume(Result.failure(BleException.PermissionDenied))
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package llc.arma.ble.data
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.bluetooth.BluetoothGatt
|
||||
import android.bluetooth.BluetoothGattCallback
|
||||
|
|
@ -13,7 +12,7 @@ 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 java.util.*
|
||||
import java.util.stream.Collectors
|
||||
|
||||
enum class Property {
|
||||
DATA_SIZE, PACKAGE
|
||||
|
|
@ -24,12 +23,16 @@ class ReadHistoryCallback(
|
|||
private val onResult: (Result<ProgressState<List<Ble.Thermometer.MeasurePoint>>, BleException>) -> Unit
|
||||
) : BluetoothGattCallback() {
|
||||
|
||||
private fun ByteArray.getUIntAt(idx: Int) =
|
||||
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 {
|
||||
|
|
@ -122,6 +125,7 @@ class ReadHistoryCallback(
|
|||
onCommonCharacteristicRead(gatt, characteristic, value, status)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
private fun onCommonCharacteristicRead(
|
||||
gatt: BluetoothGatt,
|
||||
characteristic: BluetoothGattCharacteristic,
|
||||
|
|
@ -161,30 +165,29 @@ class ReadHistoryCallback(
|
|||
|
||||
if(value[0] == 250.toByte()){
|
||||
|
||||
bleMeasureInterval = value.getUIntAt(2).toLong()
|
||||
bleLastMeasureTime = value.getUIntAt(6).toLong()
|
||||
bleRealTime = value.getUIntAt(10).toLong()
|
||||
bleMeasureInterval = value.get4byteUIntAt(4).toLong()
|
||||
bleLastMeasureTime = value.get4byteUIntAt(8).toLong()
|
||||
bleRealTime = value.get4byteUIntAt(12).toLong()
|
||||
|
||||
lastMeasureSystemTime = System.currentTimeMillis() - ((bleRealTime!! - bleLastMeasureTime!!) / 10_000)
|
||||
lastMeasureSystemTime = System.currentTimeMillis() - ((bleRealTime!! - bleLastMeasureTime!!) * 1_000)
|
||||
|
||||
val temperatureDataArray = value.asList().subList(14, value.size)
|
||||
val temperatureDataArray = value.toUByteArray().asList().subList(16, value.size)
|
||||
|
||||
resultTemperaturePackage.addAll(
|
||||
temperatureDataArray.chunked(2).map {
|
||||
(it[0] + it[1] * 256).toFloat() / 100f
|
||||
(it[0] + it[1] * 256u).toFloat() / 100f
|
||||
}.toMutableList()
|
||||
)
|
||||
|
||||
val totalDataSize = value[1].toUByte().toInt() + temperatureDataArray.size / 2
|
||||
val totalDataSize = value.get2byteUIntAt(2).toInt() + temperatureDataArray.size / 2
|
||||
|
||||
val nextPackageDataCount = value[1].toUByte()
|
||||
val nextPackageDataCount = value.get2byteUIntAt(2)
|
||||
expectedDataSize = nextPackageDataCount.toInt() + resultTemperaturePackage.size
|
||||
|
||||
onResult(Result.success(ProgressState.Progress(0f / totalDataSize.toFloat())))
|
||||
onResult(Result.success(ProgressState.Progress(nextPackageDataCount.toFloat() / totalDataSize.toFloat())))
|
||||
|
||||
if(nextPackageDataCount != 0.toUByte()){
|
||||
|
||||
if(nextPackageDataCount != 0.toUInt()){
|
||||
|
||||
if (checkPermission()) {
|
||||
|
||||
|
|
@ -218,18 +221,18 @@ class ReadHistoryCallback(
|
|||
|
||||
if (value[0] == 251.toByte()) {
|
||||
|
||||
val nextPackageDataCount = value[1].toUByte()
|
||||
val temperatureDataArray = value.toList().subList(2, value.size)
|
||||
val nextPackageDataCount = value.get2byteUIntAt(2)
|
||||
val temperatureDataArray = value.toUByteArray().toList().subList(4, value.size)
|
||||
|
||||
resultTemperaturePackage.addAll(
|
||||
temperatureDataArray.chunked(2).map {
|
||||
(it[0] + it[1] * 256).toFloat() / 100f
|
||||
(it[0] + it[1] * 256u).toFloat() / 100f
|
||||
}
|
||||
)
|
||||
|
||||
onResult(Result.success(ProgressState.Progress(expectedDataSize!!.toFloat() / resultTemperaturePackage.size.toFloat())))
|
||||
|
||||
if (nextPackageDataCount != 0.toUByte()) {
|
||||
if (nextPackageDataCount != 0.toUInt()) {
|
||||
|
||||
val writeData = byteArrayOf(5)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,245 @@
|
|||
package llc.arma.ble.data
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Application
|
||||
import android.bluetooth.BluetoothGatt
|
||||
import android.bluetooth.BluetoothGattCallback
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
import android.bluetooth.BluetoothProfile
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.core.app.ActivityCompat
|
||||
import llc.arma.ble.domain.Result
|
||||
import llc.arma.ble.domain.common.BleException
|
||||
import llc.arma.ble.domain.model.Ble
|
||||
import java.util.UUID
|
||||
|
||||
class WriteThermometerCallback(
|
||||
private val app: Application,
|
||||
private var request: Ble.Thermometer.WriteRequest,
|
||||
private val onResult: (Result<Unit, BleException>) -> Unit
|
||||
) : BluetoothGattCallback() {
|
||||
|
||||
private var flashed = false
|
||||
|
||||
override fun onConnectionStateChange(
|
||||
gatt: BluetoothGatt,
|
||||
status: Int,
|
||||
newState: Int
|
||||
) {
|
||||
super.onConnectionStateChange(gatt, status, newState)
|
||||
|
||||
Log.d("th", "onConnectionStateChange $status $newState")
|
||||
|
||||
if(checkPermission()) {
|
||||
|
||||
if(status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
|
||||
|
||||
gatt.discoverServices()
|
||||
|
||||
} else {
|
||||
|
||||
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
onResult(Result.failure(BleException.PermissionDenied))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onServicesDiscovered(
|
||||
gatt: BluetoothGatt,
|
||||
status: Int
|
||||
) {
|
||||
Log.d("th", "onServicesDiscovered $status")
|
||||
super.onServicesDiscovered(gatt, status)
|
||||
onCycle(gatt, status)
|
||||
|
||||
}
|
||||
|
||||
private fun onCycle(
|
||||
gatt: BluetoothGatt,
|
||||
status: Int
|
||||
){
|
||||
|
||||
if(request.tx != null || request.saveHistory != null || request.historyInterval != null) {
|
||||
|
||||
fun UInt.to4ByteArrayInBigEndian(): ByteArray =
|
||||
(3 downTo 0).map {
|
||||
(this shr (it * Byte.SIZE_BITS)).toByte()
|
||||
}.reversed().toByteArray()
|
||||
|
||||
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().to4ByteArrayInBigEndian().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 {
|
||||
|
||||
this.request = request.copy(
|
||||
tx = null
|
||||
)
|
||||
|
||||
Pair(
|
||||
txWriteUUID,
|
||||
byteArrayOf(
|
||||
when (it) {
|
||||
Ble.BleState.TX.MINUS_40 -> -40
|
||||
Ble.BleState.TX.MINUS_20 -> -20
|
||||
Ble.BleState.TX.MINUS_16 -> -16
|
||||
Ble.BleState.TX.MINUS_12 -> -12
|
||||
Ble.BleState.TX.MINUS_8 -> -8
|
||||
Ble.BleState.TX.MINUS_4 -> -4
|
||||
Ble.BleState.TX.ZERO -> 0
|
||||
Ble.BleState.TX.PLUS_3 -> 3
|
||||
Ble.BleState.TX.PLUS_4 -> 4
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
} ?: uuid
|
||||
|
||||
uuid?.let { uuid ->
|
||||
|
||||
gatt.services.firstOrNull { it.uuid == serviceUUID }?.characteristics?.firstOrNull {
|
||||
it.uuid == uuid.first
|
||||
}?.let {
|
||||
|
||||
gatt.writeCharacteristic(it, uuid.second)
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||
|
||||
} else {
|
||||
|
||||
if(flashed.not()){
|
||||
|
||||
flashed = true
|
||||
|
||||
gatt.services.firstOrNull { it.uuid == serviceUUID }?.characteristics?.firstOrNull {
|
||||
it.uuid == flashWriteUUID
|
||||
}?.let {
|
||||
|
||||
gatt.writeCharacteristic(it, byteArrayOf(9))
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||
|
||||
} else {
|
||||
|
||||
onResult(Result.success(Unit))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onCharacteristicWrite(
|
||||
gatt: BluetoothGatt,
|
||||
characteristic: BluetoothGattCharacteristic,
|
||||
status: Int
|
||||
) {
|
||||
|
||||
Log.d("th", "onCharacteristicWrite $status")
|
||||
|
||||
super.onCharacteristicWrite(gatt, characteristic, status)
|
||||
|
||||
if(checkPermission()) {
|
||||
|
||||
if(status == BluetoothGatt.GATT_SUCCESS || flashed) {
|
||||
|
||||
onCycle(gatt, status)
|
||||
|
||||
} else {
|
||||
|
||||
onResult(Result.failure(BleException.UnexpectedResponse))
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
onResult(Result.failure(BleException.PermissionDenied))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun BluetoothGatt.writeCharacteristic(
|
||||
characteristic: BluetoothGattCharacteristic,
|
||||
data: ByteArray
|
||||
): Result<Unit, BleException> {
|
||||
|
||||
return if(checkPermission()){
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
writeCharacteristic(characteristic, data, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
|
||||
}else{
|
||||
|
||||
characteristic.writeType
|
||||
characteristic.value = data
|
||||
writeCharacteristic(characteristic)
|
||||
}
|
||||
|
||||
Result.success(Unit)
|
||||
|
||||
} else {
|
||||
Result.failure(BleException.PermissionDenied)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun checkPermission(): Boolean {
|
||||
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_CONNECT) ==
|
||||
PackageManager.PERMISSION_GRANTED &&
|
||||
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_SCAN) ==
|
||||
PackageManager.PERMISSION_GRANTED
|
||||
} else {
|
||||
return ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_FINE_LOCATION) ==
|
||||
PackageManager.PERMISSION_GRANTED &&
|
||||
ActivityCompat.checkSelfPermission(app, Manifest.permission.ACCESS_COARSE_LOCATION) ==
|
||||
PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -26,13 +26,13 @@ sealed class Ble(
|
|||
val value: Float
|
||||
)
|
||||
|
||||
class ThermometerState(
|
||||
data class ThermometerState(
|
||||
val temperature: Float,
|
||||
val saveHistory: Boolean,
|
||||
val historyInterval: Long
|
||||
)
|
||||
|
||||
class WriteRequest(
|
||||
data class WriteRequest(
|
||||
val tx: BleState.TX?,
|
||||
val saveHistory: Boolean?,
|
||||
val historyInterval: Long?
|
||||
|
|
@ -40,7 +40,7 @@ sealed class Ble(
|
|||
|
||||
}
|
||||
|
||||
class BleState(
|
||||
data class BleState(
|
||||
val tx: TX
|
||||
){
|
||||
|
||||
|
|
|
|||
|
|
@ -2,16 +2,16 @@ package llc.arma.ble.domain.model
|
|||
|
||||
import java.util.UUID
|
||||
|
||||
class BleInfo(
|
||||
data class BleInfo(
|
||||
val name: String,
|
||||
val serial: String,
|
||||
val batteryLevel: Int,
|
||||
val rssi: Int,
|
||||
val rssi: Int?,
|
||||
val type: Type
|
||||
){
|
||||
|
||||
enum class Type(val serviceUUID: String?) {
|
||||
BEACON(null), THERMOMETER("a77db03a-9bc4-11ed-a8fc-0242ac120002")
|
||||
enum class Type {
|
||||
BEACON, THERMOMETER
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ interface BleRepository {
|
|||
|
||||
fun getBleAroundFlow(): Flow<Result<List<BleInfo>, BleException>>
|
||||
|
||||
suspend fun getBleBySerial(serial: String): Result<Ble, GetBleBySerial.GetBleException>
|
||||
suspend fun getBleBySerial(serial: String) : Result<Flow<Ble>, BleException>
|
||||
|
||||
suspend fun getTemperatureHistoryBySerial(serial: String): Flow<Result<ProgressState<List<Ble.Thermometer.MeasurePoint>>, BleException>>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
package llc.arma.ble.domain.usecase
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import llc.arma.ble.domain.model.Ble
|
||||
import llc.arma.ble.domain.repository.BleRepository
|
||||
import javax.inject.Inject
|
||||
import llc.arma.ble.domain.Result
|
||||
import llc.arma.ble.domain.common.BleException
|
||||
|
||||
class GetBleBySerial @Inject constructor(
|
||||
private val bleRepository: BleRepository
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(serial: String): Result<Ble, GetBleException> =
|
||||
suspend operator fun invoke(serial: String): Result<Flow<Ble>, BleException> =
|
||||
bleRepository.getBleBySerial(serial)
|
||||
|
||||
sealed class GetBleException {
|
||||
|
|
|
|||
Loading…
Reference in New Issue