Conflicts:
	.idea/misc.xml
This commit is contained in:
Vineyro 2023-06-05 11:40:43 +07:00
commit e8853d8cda
10 changed files with 753 additions and 45 deletions

View File

@ -1,6 +1,6 @@
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="temurin-11" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@ -11,19 +11,55 @@ class BleListContract {
sealed class Event : ViewEvent {
object OnResetFilter : Event()
object OnHideFilter : Event()
object OnShowFilter : Event()
data class OnConnectToBle(
val bleAddress: String
) : Event()
data class OnRssiRangeChanged(
val rssi: ClosedFloatingPointRange<Float>
) : Event()
data class OnMacFilterChanged(
val mac: String
) : Event()
data class OnNameFilterChanged(
val name: String
) : Event()
data class OnTypeChanged(
val type: BleInfo.Type?
) : Event()
}
data class State(
val connectedBleList: List<ConnectedBleInfo>,
val bleList: List<BleInfo>
) : ViewState
val bleList: List<BleInfo>,
val filter: Filter
) : ViewState {
data class Filter(
val name: String = "",
val mac: String = "",
val rssi: ClosedFloatingPointRange<Float> = (-100f)..(-30f),
val bleType: BleInfo.Type? = null
)
}
sealed class Effect : ViewSideEffect {
object ShowFilter : Effect()
object HideFilter : Effect()
sealed class Navigation : Effect() {
data class NavigateToBle(

View File

@ -1,5 +1,6 @@
package llc.arma.ble.app.ui.screen.ble
import android.os.SystemClock
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
@ -7,11 +8,16 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ContentAlpha
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
@ -19,8 +25,11 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import llc.arma.ble.app.ui.common.rememberBottomDialogState
import llc.arma.ble.domain.model.BleInfo
import llc.arma.ble.domain.model.ConnectedBleInfo
@ -33,19 +42,80 @@ fun BleListScreen(
val viewModel = hiltViewModel<BleListViewModel>()
val state = viewModel.viewState.value
val bottomDialog = rememberBottomDialogState()
LaunchedEffect("effect"){
viewModel.effect.onEach {
when(it){
is BleListContract.Effect.Navigation -> onNavigationEvent(it)
is BleListContract.Effect.HideFilter -> launch {
bottomDialog.hide()
}
is BleListContract.Effect.ShowFilter -> launch {
bottomDialog.show {
Filter(
filter = viewModel.viewState.value.filter,
onEvent = {
viewModel.setEvent(it)
}
)
}
}
}
}.launchIn(this)
}
Column {
CenterAlignedTopAppBar(
TopAppBar(
title = {
Text(text = "Arma BLE")
},
actions = {
Row(
modifier = Modifier
.padding(horizontal = 8.dp)
.align(Alignment.CenterVertically)
) {
Text(text = "${state.bleList.size}")
Spacer(modifier = Modifier.width(12.dp))
Text(text = "${state.bleList.filter {
it.batteryLevel == 100
}.filterNot { SystemClock.elapsedRealtime() - it.scanTime > 10_000 }.size}")
Text(text = " | ")
Text(
text = "${state.bleList.filter { SystemClock.elapsedRealtime() - it.scanTime > 10_000 }.size}",
color = LocalContentColor.current.copy(alpha = ContentAlpha.disabled)
)
Text(text = " | ")
Text(
text = "${state.bleList.filter { it.batteryLevel < 100 }.size}",
color = MaterialTheme.colorScheme.error
)
}
IconButton(
onClick = {
viewModel.setEvent(BleListContract.Event.OnShowFilter)
}
) {
Icon(
imageVector = Icons.Rounded.FilterAlt,
contentDescription = null
)
}
}
)
@ -71,7 +141,14 @@ fun BleListScreen(
}
items(items = state.bleList) {
val filteredData = state.bleList.filter {
(it.type == state.filter.bleType || state.filter.bleType == null) &&
it.name.contains(state.filter.name) &&
it.serial.contains(state.filter.mac) &&
state.filter.rssi.contains(it.rssi?.toFloat() ?: Float.MIN_VALUE)
}
items(items = filteredData.sortedBy { it.name }.reversed()) {
BleItem(
ble = it,
@ -113,14 +190,48 @@ private fun BleItem(
onClick: () -> Unit
){
val color = if(ble.batteryLevel < 100){
MaterialTheme.colorScheme.errorContainer
} else {
MaterialTheme.colorScheme.background
}
val highAlpha = ContentAlpha.high
val disabledAlpha = ContentAlpha.disabled
var alpha by remember {
mutableStateOf(
if(SystemClock.elapsedRealtime() - ble.scanTime > 10_000){
disabledAlpha
} else {
highAlpha
}
)
}
LaunchedEffect(ble.scanTime) {
while(true) {
alpha = if(SystemClock.elapsedRealtime() - ble.scanTime > 10_000){
disabledAlpha
} else {
highAlpha
}
delay(800)
}
}
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(16.dp))
.background(color)
.clickable { onClick() }
.padding(vertical = 8.dp, horizontal = 16.dp)
.alpha(alpha)
) {
ItemIcon {
@ -158,10 +269,20 @@ private fun BleItem(
contentDescription = null
)
Text(
style = MaterialTheme.typography.bodyMedium,
text = ble.rssi.toString() + " dBm"
)
Box {
Text(
style = MaterialTheme.typography.bodyMedium,
text = "-999 dBm",
modifier = Modifier.alpha(0f)
)
Text(
style = MaterialTheme.typography.bodyMedium,
text = ble.rssi.toString() + " dBm"
)
}
}
@ -170,19 +291,87 @@ private fun BleItem(
modifier = Modifier.alpha(0.7f)
) {
val color = if(ble.batteryLevel < 100){
MaterialTheme.colorScheme.error
} else {
LocalContentColor.current
}
Icon(
modifier = Modifier.size(16.dp),
imageVector = Icons.Rounded.BatteryFull,
contentDescription = null
contentDescription = null,
tint = color
)
Text(
style = MaterialTheme.typography.bodyMedium,
text = ble.batteryLevel.toString() + " %"
)
Box {
Text(
style = MaterialTheme.typography.bodyMedium,
text = "100 %",
modifier = Modifier.alpha(0f)
)
Text(
style = MaterialTheme.typography.bodyMedium,
text = ble.batteryLevel.toString() + " %",
color = color
)
}
}
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.alpha(0.7f)
) {
val color = if(ble.batteryLevel < 100){
MaterialTheme.colorScheme.error
} else {
LocalContentColor.current
}
Icon(
modifier = Modifier.size(16.dp),
imageVector = Icons.Rounded.ArrowRightAlt,
contentDescription = null,
tint = color
)
Box {
Text(
style = MaterialTheme.typography.bodyMedium,
text = "99s",
modifier = Modifier.alpha(0f)
)
Text(
style = MaterialTheme.typography.bodyMedium,
text = ">1m",
modifier = Modifier.alpha(0f)
)
val lastAdv = ((SystemClock.elapsedRealtime() - ble.scanTime) / 1_000)
Text(
style = MaterialTheme.typography.bodyMedium,
text = if(lastAdv > 60){
">1m"
} else {
"${lastAdv}s"
}
)
}
}
}
}

View File

@ -23,7 +23,7 @@ class BleListViewModel @Inject constructor(
it.fold(
onSuccess = {
setState {
BleListContract.State(
copy(
connectedBleList = emptyList(),
bleList = it
)
@ -38,11 +38,18 @@ class BleListViewModel @Inject constructor(
}
override fun setInitialState(): BleListContract.State = BleListContract.State(emptyList(), emptyList())
override fun setInitialState(): BleListContract.State = BleListContract.State(emptyList(), emptyList(), BleListContract.State.Filter())
override fun handleEvents(event: BleListContract.Event) {
when(event){
is BleListContract.Event.OnConnectToBle -> reduce(viewState.value, event)
is BleListContract.Event.OnHideFilter -> reduce(viewState.value, event)
is BleListContract.Event.OnMacFilterChanged -> reduce(viewState.value, event)
is BleListContract.Event.OnNameFilterChanged -> reduce(viewState.value, event)
is BleListContract.Event.OnResetFilter -> reduce(viewState.value, event)
is BleListContract.Event.OnRssiRangeChanged -> reduce(viewState.value, event)
is BleListContract.Event.OnShowFilter -> reduce(viewState.value, event)
is BleListContract.Event.OnTypeChanged -> reduce(viewState.value, event)
}
}
@ -55,4 +62,83 @@ class BleListViewModel @Inject constructor(
}
}
private fun reduce(
state: BleListContract.State,
event: BleListContract.Event.OnHideFilter
) {
setEffect {
BleListContract.Effect.HideFilter
}
}
private fun reduce(
state: BleListContract.State,
event: BleListContract.Event.OnMacFilterChanged
) {
setState {
copy(
filter = this.filter.copy(mac = event.mac)
)
}
}
private fun reduce(
state: BleListContract.State,
event: BleListContract.Event.OnNameFilterChanged
) {
setState {
copy(
filter = this.filter.copy(name = event.name)
)
}
}
private fun reduce(
state: BleListContract.State,
event: BleListContract.Event.OnResetFilter
) {
setState {
copy(
filter = BleListContract.State.Filter()
)
}
setEffect {
BleListContract.Effect.HideFilter
}
}
private fun reduce(
state: BleListContract.State,
event: BleListContract.Event.OnRssiRangeChanged
) {
setState {
copy(
filter = this.filter.copy(rssi = event.rssi)
)
}
}
private fun reduce(
state: BleListContract.State,
event: BleListContract.Event.OnTypeChanged
) {
setState {
copy(
filter = this.filter.copy(bleType = event.type)
)
}
}
private fun reduce(
state: BleListContract.State,
event: BleListContract.Event.OnShowFilter
) {
setEffect {
BleListContract.Effect.ShowFilter
}
}
}

View File

@ -0,0 +1,337 @@
package llc.arma.ble.app.ui.screen.ble
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Bluetooth
import androidx.compose.material.icons.rounded.Close
import androidx.compose.material.icons.rounded.Search
import androidx.compose.material.icons.rounded.ShortText
import androidx.compose.material.icons.rounded.SignalCellularAlt
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.RangeSlider
import androidx.compose.material3.SliderDefaults
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import llc.arma.ble.domain.model.BleInfo
private val BleInfo.Type?.localized: String
get() {
return when(this){
BleInfo.Type.BEACON -> "Маяк"
BleInfo.Type.THERMOMETER -> "Термодатчик"
null -> "Все"
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Filter(
filter: BleListContract.State.Filter,
onEvent: (BleListContract.Event) -> Unit
) {
Column(
) {
Text(
modifier = Modifier.padding(horizontal = 12.dp),
text = "Фильтр",
style = MaterialTheme.typography.titleLarge
)
Spacer(modifier = Modifier.height(16.dp))
Column(
modifier = Modifier.padding(horizontal = 12.dp)
) {
Spacer(modifier = Modifier.height(8.dp))
Row(
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Rounded.Bluetooth,
contentDescription = null,
modifier = Modifier.padding(12.dp)
)
var expanded by remember { mutableStateOf(false) }
ExposedDropdownMenuBox(
modifier = Modifier.fillMaxWidth().padding(end = 8.dp),
expanded = expanded,
onExpandedChange = {
expanded = it
}
) {
OutlinedTextField(
modifier = Modifier.menuAnchor().fillMaxWidth(),
readOnly = true,
value = filter.bleType.localized,
onValueChange = { },
label = { Text("Тип") },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(
expanded = expanded
)
}
)
ExposedDropdownMenu(
modifier = Modifier.background(MaterialTheme.colorScheme.background).fillMaxWidth(),
expanded = expanded,
onDismissRequest = {
expanded = false
}
) {
mutableListOf<BleInfo.Type?>(null).apply {
addAll(BleInfo.Type.values())
}.forEach { selectionOption ->
DropdownMenuItem(
onClick = {
onEvent(
BleListContract.Event.OnTypeChanged(
selectionOption
)
)
expanded = false
},
text = {
Text(text = selectionOption.localized)
}
)
}
}
}
}
Row(
verticalAlignment = Alignment.CenterVertically
){
Icon(
imageVector = Icons.Rounded.Search,
contentDescription = null,
modifier = Modifier.padding(12.dp)
)
OutlinedTextField(
value = filter.name,
singleLine = true,
onValueChange = {
onEvent(BleListContract.Event.OnNameFilterChanged(it))
},
label = {
Text(text = "Имя")
},
trailingIcon = {
if(filter.name.isNotEmpty()) {
IconButton(
onClick = { onEvent(BleListContract.Event.OnNameFilterChanged("")) }
) {
Icon(
imageVector = Icons.Rounded.Close,
contentDescription = null
)
}
}
},
modifier = Modifier
.padding(end = 8.dp)
.fillMaxWidth()
)
}
Spacer(modifier = Modifier.height(8.dp))
Row(
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = Icons.Rounded.ShortText,
contentDescription = null,
modifier = Modifier.padding(12.dp)
)
OutlinedTextField(
value = filter.mac,
singleLine = true,
onValueChange = {
onEvent(BleListContract.Event.OnMacFilterChanged(it))
},
label = {
Text(text = "Mac")
},
trailingIcon = {
if (filter.mac.isNotEmpty()) {
IconButton(
onClick = { onEvent(BleListContract.Event.OnMacFilterChanged("")) }
) {
Icon(
imageVector = Icons.Rounded.Close,
contentDescription = null
)
}
}
},
modifier = Modifier
.padding(end = 8.dp)
.fillMaxWidth()
)
}
Spacer(modifier = Modifier.height(12.dp))
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(end = 8.dp)
) {
Icon(
imageVector = Icons.Rounded.SignalCellularAlt,
contentDescription = null,
modifier = Modifier.padding(12.dp)
)
Column() {
RangeSlider(
value = filter.rssi,
onValueChange = {
onEvent(BleListContract.Event.OnRssiRangeChanged(it))
},
valueRange = (-100f)..(-30f),
steps = 69,
colors = SliderDefaults.colors(
activeTickColor = MaterialTheme.colorScheme.primary,
inactiveTickColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.38f)
)
)
Row() {
Text(text = filter.rssi.start.toInt().toString() + " dBm")
Spacer(modifier = Modifier.weight(1f))
Text(text = filter.rssi.endInclusive.toInt().toString() + " dBm")
}
}
}
Spacer(modifier = Modifier.height(20.dp))
Box(
modifier = Modifier
) {
Surface(
modifier = Modifier
.fillMaxWidth()
.height(50.dp),
shape = CircleShape,
color = MaterialTheme.colorScheme.primaryContainer,
onClick = {
onEvent(BleListContract.Event.OnHideFilter)
}
) {
Box(modifier = Modifier.fillMaxSize()) {
Text(
modifier = Modifier.align(Alignment.Center),
color = MaterialTheme.colorScheme.background,
style = MaterialTheme.typography.labelLarge,
text = "Применить"
)
}
}
}
Spacer(modifier = Modifier.height(8.dp))
Box(
modifier = Modifier
) {
Surface(
modifier = Modifier
.fillMaxWidth()
.height(50.dp),
shape = CircleShape,
color = MaterialTheme.colorScheme.secondaryContainer,
onClick = {
onEvent(BleListContract.Event.OnResetFilter)
}
) {
Box(modifier = Modifier.fillMaxSize()) {
Text(
modifier = Modifier.align(Alignment.Center),
color = MaterialTheme.colorScheme.onSecondaryContainer,
style = MaterialTheme.typography.labelLarge,
text = "Сбросить"
)
}
}
}
Spacer(modifier = Modifier.height(8.dp))
}
}
}

View File

@ -185,10 +185,14 @@ fun DisplayState(
Text(
text = "Интервал измерений"
)
val hours = ble.thermometerState.historyInterval / 1000 / 60 / 60
val minutes = (ble.thermometerState.historyInterval - ( hours * 1000 * 60 * 60 )) / 1000 / 60
Text(
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.bodyMedium,
text = "${ble.thermometerState.historyInterval / 1000 / 60 / 60} ч."
text = "$hours ч. $minutes мин."
)
}

View File

@ -4,7 +4,6 @@ import android.Manifest
import android.app.Application
import android.bluetooth.*
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings
import android.content.pm.PackageManager
@ -24,13 +23,10 @@ import llc.arma.ble.domain.model.Ble
import llc.arma.ble.domain.model.BleInfo
import llc.arma.ble.domain.model.ConnectedBleInfo
import llc.arma.ble.domain.repository.BleRepository
import llc.arma.ble.domain.usecase.GetBleBySerial
import java.nio.charset.Charset
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
val serviceUUID: UUID = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002")
@ -43,6 +39,19 @@ val passwordWriteUUID: UUID = UUID.fromString("a77db6f2-9bc4-11ed-a8fc-0242ac120
val txWriteUUID: UUID = UUID.fromString("00002a07-0000-1000-8000-00805f9b34fb")
val flashWriteUUID: UUID = UUID.fromString("a77db6f2-9bc4-11ed-a8fc-0242ac120002")
@OptIn(ExperimentalUnsignedTypes::class)
fun UByteArray.toTemperature(): Float {
val uShort = (this[0] + this[1] * 256u).toUShort()
val result = if (uShort > Short.MAX_VALUE.toUShort()) {
((uShort.inv() + 1u).toFloat().unaryMinus()) / 100f
} else {
uShort.toFloat() / 100f
}
return result
}
@Singleton
class BleRepositoryImpl @Inject constructor(
private val app: Application
@ -55,7 +64,8 @@ class BleRepositoryImpl @Inject constructor(
serial = device.address,
batteryLevel = batteryLevel ?: 0,
rssi = rssi,
type = type
type = type,
scanTime = timestampNanos / 1_000_000
)
}
@ -85,7 +95,7 @@ class BleRepositoryImpl @Inject constructor(
(this[idx].toUInt() and 0xFFu)
private val deviceCache = mutableMapOf<String, ScanResult>()
val resultList = mutableMapOf<String, BleInfo>()
val resultList: MutableMap<String, BleInfo> = Collections.synchronizedMap(mutableMapOf<String, BleInfo>())
override fun getConnectedBle(): List<ConnectedBleInfo> {
if(checkPermission()) {
@ -143,6 +153,32 @@ class BleRepositoryImpl @Inject constructor(
}
override fun onBatchScanResults(results: MutableList<ScanResult>) {
super.onBatchScanResults(results)
if (checkPermission()) {
results.forEach { result ->
if (result.scanRecord?.deviceName?.contains("ArmA") == true) {
resultList[result.device.address] = result.info
deviceCache[result.device.address] = result
}
}
} else {
CoroutineScope(Dispatchers.IO).launch {
send(
Result.failure(BleException.PermissionDenied)
)
}
}
}
}
val bleScanner =
@ -342,10 +378,22 @@ class BleRepositoryImpl @Inject constructor(
}
@OptIn(ExperimentalUnsignedTypes::class)
private suspend fun readTemperature(
record: ScanResult
): Result<Float, BleException> {
writeCharacteristic(
device = record.device,
serviceId = serviceUUID,
characteristicId = temperatureReadUUID,
writeData = byteArrayOf(1, 1)
).onFailure {
return Result.failure(it)
}
delay(2_000)
val dataResult = readCharacteristic(
device = record.device,
serviceId = serviceUUID,
@ -357,7 +405,7 @@ class BleRepositoryImpl @Inject constructor(
onSuccess = { return@fold it }
)
return Result.success((dataResult[0] + dataResult[1] * 256).toFloat() / 100f)
return Result.success(dataResult.toUByteArray().toTemperature())
}
@ -701,21 +749,22 @@ class BleRepositoryImpl @Inject constructor(
} else {
bleGatt?.close()
it.resume(Result.failure(BleException.PermissionDenied))
}
} else {
it.resume(Result.success(Unit))
bleGatt?.close()
it.resume(Result.success(Unit))
}
} else {
it.resume(Result.failure(BleException.UnexpectedResponse))
bleGatt?.close()
it.resume(Result.failure(BleException.UnexpectedResponse))
}
@ -743,6 +792,7 @@ class BleRepositoryImpl @Inject constructor(
} else {
bleGatt?.close()
it.resume(Result.failure(BleException.PermissionDenied))
}
@ -753,12 +803,12 @@ class BleRepositoryImpl @Inject constructor(
Log.d("write", "service not found")
gatt.disconnect()
bleGatt?.close()
it.resume(Result.failure(BleException.UnexpectedResponse))
} else {
gatt.disconnect()
bleGatt?.close()
it.resume(Result.failure(BleException.UnexpectedResponse))
}

View File

@ -7,6 +7,7 @@ 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
@ -132,6 +133,8 @@ class ReadHistoryCallback(
value: ByteArray,
status: Int
){
Log.d("read", value[0].toString())
if(status == BluetoothGatt.GATT_SUCCESS){
when(readProperty){
Property.DATA_SIZE -> {
@ -175,17 +178,15 @@ class ReadHistoryCallback(
resultTemperaturePackage.addAll(
temperatureDataArray.chunked(2).map {
(it[0] + it[1] * 256u).toFloat() / 100f
it.toUByteArray().toTemperature()
}.toMutableList()
)
val totalDataSize = value.get2byteUIntAt(2).toInt() + temperatureDataArray.size / 2
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())))
Log.d("read", expectedDataSize.toString())
onResult(Result.success(ProgressState.Progress(0f / expectedDataSize!!.toFloat())))
onResult(Result.success(ProgressState.Progress(resultTemperaturePackage.size.toFloat() / expectedDataSize!!.toFloat())))
if(nextPackageDataCount != 0.toUInt()){
@ -226,11 +227,11 @@ class ReadHistoryCallback(
resultTemperaturePackage.addAll(
temperatureDataArray.chunked(2).map {
(it[0] + it[1] * 256u).toFloat() / 100f
it.toUByteArray().toTemperature()
}
)
onResult(Result.success(ProgressState.Progress(expectedDataSize!!.toFloat() / resultTemperaturePackage.size.toFloat())))
onResult(Result.success(ProgressState.Progress(resultTemperaturePackage.size.toFloat() / expectedDataSize!!.toFloat())))
if (nextPackageDataCount != 0.toUInt()) {

View File

@ -10,6 +10,10 @@ import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import androidx.core.app.ActivityCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import llc.arma.ble.domain.Result
import llc.arma.ble.domain.common.BleException
import llc.arma.ble.domain.model.Ble
@ -69,10 +73,10 @@ class WriteThermometerCallback(
if(request.tx != null || request.saveHistory != null || request.historyInterval != null) {
fun UInt.to4ByteArrayInBigEndian(): ByteArray =
fun UInt.to4ByteArrayInLittleEndian(): ByteArray =
(3 downTo 0).map {
(this shr (it * Byte.SIZE_BITS)).toByte()
}.reversed().toByteArray()
}.toByteArray()
var uuid: Pair<UUID, ByteArray>? = null
@ -85,7 +89,7 @@ class WriteThermometerCallback(
Pair(
intervalWriteUUID,
mutableListOf<Byte>(3).apply {
addAll((it).toUInt().to4ByteArrayInBigEndian().toList())
addAll((it).toUInt().to4ByteArrayInLittleEndian().reversed().toList())
}.toByteArray()
)
}
@ -203,18 +207,20 @@ class WriteThermometerCallback(
}
fun BluetoothGatt.writeCharacteristic(
private fun BluetoothGatt.writeCharacteristic(
characteristic: BluetoothGattCharacteristic,
data: ByteArray
): Result<Unit, BleException> {
return if(checkPermission()){
Log.d("write", data.asUByteArray().joinToString(" ") { it.toString(16).padStart(2, '0') })
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
writeCharacteristic(characteristic, data, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
}else{
characteristic.writeType
characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
characteristic.value = data
writeCharacteristic(characteristic)
}
@ -227,7 +233,7 @@ class WriteThermometerCallback(
}
fun checkPermission(): Boolean {
private fun checkPermission(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
ActivityCompat.checkSelfPermission(app, Manifest.permission.BLUETOOTH_CONNECT) ==

View File

@ -1,13 +1,12 @@
package llc.arma.ble.domain.model
import java.util.UUID
data class BleInfo(
val name: String,
val serial: String,
val batteryLevel: Int,
val rssi: Int?,
val type: Type
val type: Type,
val scanTime: Long
){
enum class Type {