This commit is contained in:
Vineyro 2023-03-27 17:04:27 +07:00
parent ffba61a55e
commit ce95316494
16 changed files with 448 additions and 172 deletions

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<runningDeviceTargetSelectedWithDropDown>
<Target>
<type value="RUNNING_DEVICE_TARGET" />
<deviceKey>
<Key>
<type value="SERIAL_NUMBER" />
<value value="55381e0a" />
</Key>
</deviceKey>
</Target>
</runningDeviceTargetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2023-03-22T08:48:05.794601500Z" />
</component>
</project>

View File

@ -2,11 +2,18 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation"
tools:targetApi="s" />
<uses-feature android:name="android.hardware.location.gps" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
<application
android:allowBackup="true"

View File

@ -1,5 +1,6 @@
package llc.arma.ble.app.ui
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
@ -7,28 +8,65 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.core.view.WindowCompat
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberMultiplePermissionsState
import dagger.hilt.android.AndroidEntryPoint
import llc.arma.ble.app.ui.screen.main.MainScreen
import llc.arma.ble.app.ui.theme.BleTheme
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@OptIn(ExperimentalPermissionsApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
BleTheme {
Surface(
modifier = Modifier.fillMaxSize().navigationBarsPadding(),
modifier = Modifier
.fillMaxSize()
.navigationBarsPadding(),
color = MaterialTheme.colorScheme.background
) {
val multiplePermissionsState = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
rememberMultiplePermissionsState(
listOf(
android.Manifest.permission.BLUETOOTH_SCAN,
android.Manifest.permission.BLUETOOTH_CONNECT
)
)
} else {
rememberMultiplePermissionsState(
listOf()
)
}
if(multiplePermissionsState.allPermissionsGranted) {
MainScreen()
} else {
LaunchedEffect(multiplePermissionsState){
multiplePermissionsState.launchMultiplePermissionRequest()
}
}
}
}
}
}
}

View File

@ -18,6 +18,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
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.flow.launchIn
@ -49,6 +50,13 @@ fun BleListScreen(
}
)
if(state.bleList.isEmpty()){
LinearProgressIndicator(
strokeCap = StrokeCap.Round,
modifier = Modifier.fillMaxWidth().height(3.dp)
)
}
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = Modifier.fillMaxSize()

View File

@ -18,9 +18,17 @@ class BleListViewModel @Inject constructor(
init {
getBleAroundFlow().onEach {
it.fold(
onSuccess = {
setState {
BleListContract.State(it)
}
},
onFailure = {
}
)
}.launchIn(viewModelScope)
}

View File

@ -12,6 +12,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import kotlinx.coroutines.flow.launchIn
@ -123,7 +124,11 @@ fun ConnectionScreen(
private fun LoadingState(){
Column {
Box(modifier = Modifier.fillMaxSize()) {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
CircularProgressIndicator(
strokeCap = StrokeCap.Round,
modifier = Modifier.align(Alignment.Center
)
)
}
}
}

View File

@ -26,6 +26,31 @@ enum class SheetPage {
INTERVAL, POWER, TEMPERATURE_HISTORY
}
private val Boolean.localizedName: String
get() {
return if(this){
"Включено"
} else {
"Выключено"
}
}
private val Ble.BleState.TX.localizedName: String
get() {
return when(this){
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
}.toString()
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ThermometerScreen(
@ -140,8 +165,6 @@ fun ThermometerScreen(
)
}
}
@ -197,7 +220,7 @@ fun ThermometerScreen(
Text(
color = MaterialTheme.colorScheme.secondary,
style = MaterialTheme.typography.bodyMedium,
text = "${it} db"
text = "${state.origin.state.tx.localizedName} db -> ${it.localizedName} db"
)
}
@ -209,6 +232,39 @@ fun ThermometerScreen(
it.writeRequest.saveHistory?.let {
Box(
modifier = Modifier.padding(
vertical = 8.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 = "${state.origin.thermometerState.saveHistory.localizedName} -> ${it.localizedName}"
)
}
}
}
}
it.writeRequest.historyInterval?.let {

View File

@ -119,19 +119,6 @@ fun DisplayState(
}
if (ble.thermometerState.temperature.loading) {
CircularProgressIndicator()
} else {
Icon(
imageVector = Icons.Rounded.Refresh,
contentDescription = null
)
}
}
}
@ -194,7 +181,7 @@ fun DisplayState(
) {
Text(
text = "Интервал измерний"
text = "Интервал измерений"
)
Text(
color = MaterialTheme.colorScheme.secondary,

View File

@ -1,5 +1,9 @@
package llc.arma.ble.app.ui.screen.thermometer.view
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
@ -32,9 +36,15 @@ import kotlin.random.Random
import kotlin.random.nextInt
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Refresh
import androidx.compose.ui.graphics.StrokeCap
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.chart.scale.AutoScaleUp
import com.patrykandpatrick.vico.core.entry.ChartEntry
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import llc.arma.ble.domain.common.ProgressState
import llc.arma.ble.domain.model.Ble
import java.text.SimpleDateFormat
import java.util.*
@ -80,7 +90,7 @@ fun TemperatureHistory(
onClick = {
viewModel.setEvent(TemperatureHistoryContract.Event.LoadHistory(ble.serial))
},
enabled = state is TemperatureHistoryContract.State.Display
enabled = state.loadingHistoryState is ProgressState.Finished
) {
Icon(
imageVector = Icons.Rounded.Refresh,
@ -92,10 +102,12 @@ fun TemperatureHistory(
Spacer(modifier = Modifier.height(16.dp))
when (state) {
is TemperatureHistoryContract.State.Display -> {
when (state.loadingHistoryState) {
is ProgressState.Finished -> {
val producer = state.history.mapIndexed { index, measurePoint ->
Text(text = "${state.loadingHistoryState.data.size}")
val producer = state.loadingHistoryState.data.mapIndexed { index, measurePoint ->
TemperatureEntry(measurePoint.date, index.toFloat(), measurePoint.value) }.let {
ChartEntryModelProducer(it)
}
@ -107,14 +119,20 @@ fun TemperatureHistory(
.orEmpty()
}
val lineChart = lineChart()
val lineChart = lineChart(
spacing = 110.dp
)
Box(modifier = Modifier.padding(8.dp)) {
val scrollState = rememberChartScrollState()
LaunchedEffect(scrollState.maxValue){
scrollState.scrollBy(scrollState.maxValue)
}
Chart(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1.5f),
chartScrollState = scrollState,
chart = lineChart,
chartModelProducer = producer,
startAxis = startAxis(),
@ -122,12 +140,14 @@ fun TemperatureHistory(
valueFormatter = axisValueFormatter,
labelRotationDegrees = 0f,
),
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1.5f),
)
}
}
is TemperatureHistoryContract.State.Loading -> {
is ProgressState.Indeterminate -> {
Box(modifier = Modifier.padding(8.dp)) {
@ -138,16 +158,38 @@ fun TemperatureHistory(
){
CircularProgressIndicator(
strokeCap = StrokeCap.Round,
modifier = Modifier.align(Alignment.Center)
)
}
}
}
is ProgressState.Progress -> Box(modifier = Modifier.padding(8.dp)) {
Box(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(2f),
){
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)
)
}
}
}
}
@ -163,15 +205,9 @@ class TemperatureHistoryContract {
}
sealed class State : ViewState {
object Loading : State()
data class Display(
var history : List<Ble.Thermometer.MeasurePoint>
) : State()
}
class State(
val loadingHistoryState : ProgressState<List<Ble.Thermometer.MeasurePoint>>
) : ViewState
sealed class Effect : ViewSideEffect {
@ -186,7 +222,9 @@ class TemperatureHistoryViewModel @Inject constructor(
private val getTemperatureHistoryBySerial: GetTemperatureHistoryBySerial
) : BaseViewModel<TemperatureHistoryContract.State, TemperatureHistoryContract.Event, TemperatureHistoryContract.Effect>() {
override fun setInitialState() = TemperatureHistoryContract.State.Loading
override fun setInitialState() = TemperatureHistoryContract.State(
ProgressState.Indeterminate
)
override fun handleEvents(event: TemperatureHistoryContract.Event) {
when(event){
@ -201,14 +239,21 @@ class TemperatureHistoryViewModel @Inject constructor(
viewModelScope.launch {
setState {
TemperatureHistoryContract.State.Loading
TemperatureHistoryContract.State(ProgressState.Indeterminate)
}
val history = getTemperatureHistoryBySerial(event.serial)
getTemperatureHistoryBySerial(event.serial).onEach {
it.fold(
onSuccess = {
setState {
TemperatureHistoryContract.State.Display(history)
TemperatureHistoryContract.State(it)
}
},
onFailure = {
}
)
}.launchIn(this)
}
}

View File

@ -8,24 +8,23 @@ import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings
import android.content.pm.PackageManager
import android.os.Build
import android.os.ParcelUuid
import android.util.Log
import androidx.core.app.ActivityCompat
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
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.model.BleInfo
import llc.arma.ble.domain.repository.BleRepository
import llc.arma.ble.domain.usecase.GetBleBySerial
import java.nio.ByteBuffer
import java.util.*
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
@Singleton
class BleRepositoryImpl @Inject constructor(
@ -69,12 +68,21 @@ class BleRepositoryImpl @Inject constructor(
(this[idx].toUInt() and 0xFFu)
private val deviceCache = mutableMapOf<String, ScanResult>()
override fun getBleAroundFlow(): Flow<List<BleInfo>> {
val resultList = mutableMapOf<String, BleInfo>()
return callbackFlow {
override fun getBleAroundFlow(): Flow<Result<List<BleInfo>, BleException>> {
return if (ActivityCompat.checkSelfPermission(
app,
Manifest.permission.BLUETOOTH_SCAN
) != PackageManager.PERMISSION_GRANTED
) {
flow { emit(Result.failure(BleException.PermissionDenied)) }
} else {
callbackFlow {
val bleCallback = object : ScanCallback() {
@ -91,7 +99,7 @@ class BleRepositoryImpl @Inject constructor(
) == PackageManager.PERMISSION_GRANTED
) {
if(result.scanRecord?.deviceName?.contains("ArmA") == true) {
if (result.scanRecord?.deviceName?.contains("ArmA") == true) {
resultList[result.device.address] = result.info
@ -99,13 +107,21 @@ class BleRepositoryImpl @Inject constructor(
}
} else {
CoroutineScope(Dispatchers.IO).launch {
send(
Result.failure(BleException.PermissionDenied)
)
}
}
}
}
val bleScanner = app.getSystemService(BluetoothManager::class.java).adapter.bluetoothLeScanner
val bleScanner =
app.getSystemService(BluetoothManager::class.java).adapter.bluetoothLeScanner
bleScanner.startScan(
listOf(),
ScanSettings.Builder()
@ -121,7 +137,7 @@ class BleRepositoryImpl @Inject constructor(
schedule(object : TimerTask() {
override fun run() {
CoroutineScope(Dispatchers.IO).launch {
send(resultList.values.toList())
send(Result.success(resultList.values.toList()))
}
}
}, 100, 500)
@ -136,6 +152,8 @@ class BleRepositoryImpl @Inject constructor(
}
}
@OptIn(ExperimentalCoroutinesApi::class)
override suspend fun getBleBySerial(
serial: String
@ -178,10 +196,17 @@ class BleRepositoryImpl @Inject constructor(
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 = readThermometerState(result)
thermometerState = thermometer
)
}
@ -204,33 +229,54 @@ class BleRepositoryImpl @Inject constructor(
private suspend fun readThermometerState(
record: ScanResult
): Ble.Thermometer.ThermometerState {
): Result<Ble.Thermometer.ThermometerState, BleException> {
return Ble.Thermometer.ThermometerState(
temperature = readTemperature(record),
val temperature = readTemperature(record).fold(
onFailure = {
return Result.failure(it)
},
onSuccess = { return@fold it }
)
val history = readHistoryInterval(record).fold(
onFailure = {
return Result.failure(it)
},
onSuccess = { return@fold it }
)
return Result.success(
Ble.Thermometer.ThermometerState(
temperature = temperature,
saveHistory = record.timerEnabled,
historyInterval = readHistoryInterval(record)
historyInterval = history
)
)
}
private suspend fun readTemperature(
record: ScanResult
): Float {
): Result<Float, BleException> {
val dataResult = readCharacteristic(
device = record.device,
serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"),
characteristicId = UUID.fromString("00002a6e-0000-1000-8000-00805f9b34fb")
).fold(
onFailure = {
return Result.failure(it)
},
onSuccess = { return@fold it }
)
return (dataResult[0] + dataResult[1] * 256).toFloat() / 100f
return Result.success((dataResult[0] + dataResult[1] * 256).toFloat() / 100f)
}
private suspend fun readHistoryInterval(
record: ScanResult
): Long {
): Result<Long, BleException> {
writeCharacteristic(
device = record.device,
@ -243,19 +289,26 @@ class BleRepositoryImpl @Inject constructor(
device = record.device,
serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"),
characteristicId = UUID.fromString("0000b2d8-0000-1000-8000-00805f9b34fb")
).fold(
onFailure = {
return Result.failure(it)
},
onSuccess = { return@fold it }
)
return if(dataResult.size == 4){
return Result.success(
if(dataResult.size == 4){
dataResult.getUIntAt(0).toLong()
}else{
0
}
)
}
override suspend fun getTemperatureHistoryBySerial(
serial: String
): List<Ble.Thermometer.MeasurePoint> {
): Flow<Result<ProgressState<List<Ble.Thermometer.MeasurePoint>>, BleException>> = flow {
fun ByteArray.getUIntAt(idx: Int) =
((this[idx + 3].toUInt() and 0xFFu) shl 24) or
@ -265,6 +318,8 @@ class BleRepositoryImpl @Inject constructor(
deviceCache[serial]?.device?.let { device ->
emit(Result.success(ProgressState.Indeterminate))
writeCharacteristic(
device = device,
serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"),
@ -276,6 +331,12 @@ class BleRepositoryImpl @Inject constructor(
device = device,
serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"),
characteristicId = UUID.fromString("0000b2d8-0000-1000-8000-00805f9b34fb"),
).fold(
onFailure = {
emit(Result.failure(it))
return@flow
},
onSuccess = { return@fold it }
)
writeCharacteristic(
@ -295,6 +356,12 @@ class BleRepositoryImpl @Inject constructor(
device = device,
serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"),
characteristicId = UUID.fromString("0000b2d8-0000-1000-8000-00805f9b34fb"),
).fold(
onFailure = {
emit(Result.failure(it))
return@flow
},
onSuccess = { return@fold it }
)
if(firstPackageResponse[0] == 250.toByte()){
@ -311,11 +378,14 @@ class BleRepositoryImpl @Inject constructor(
(it[0] + it[1] * 256).toFloat() / 100f
}.toMutableList()
Log.d("read", temperaturePackage.size.toString())
var dataCount = firstPackageResponse[1].toUByte()
val totalDataSize = dataCount.toInt() + temperaturePackage.size
var dataCount = firstPackageResponse[1]
emit(Result.success(ProgressState.Progress(0f / totalDataSize.toFloat())))
delay(100)
emit(Result.success(ProgressState.Progress(dataCount.toFloat() / totalDataSize.toFloat())))
while(dataCount != 0.toByte()){
while(dataCount != 0.toUByte()){
writeCharacteristic(
device = device,
@ -328,9 +398,15 @@ class BleRepositoryImpl @Inject constructor(
device = device,
serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"),
characteristicId = UUID.fromString("0000b2d8-0000-1000-8000-00805f9b34fb"),
).fold(
onFailure = {
emit(Result.failure(it))
return@flow
},
onSuccess = { return@fold it }
)
dataCount = readResponse.get(1)
dataCount = readResponse[1].toUByte()
temperatureDataArray = readResponse.toList().subList(2, readResponse.size)
@ -340,25 +416,27 @@ class BleRepositoryImpl @Inject constructor(
}
)
Log.d("read",(temperatureDataArray.size / 2).toString())
emit(Result.success(ProgressState.Progress(totalDataSize.toFloat() / temperaturePackage.size.toFloat())))
}
Log.d("metadata", interval.toString() + " " + lastMeasureSystemTime.toString())
return temperaturePackage.withIndex().map {
emit(
Result.success(
ProgressState.Finished(
temperaturePackage.withIndex().map {
Ble.Thermometer.MeasurePoint(
date = lastMeasureSystemTime - (((temperaturePackage.size - 1) - it.index) * interval),
value = it.value
)
}
)
)
)
}
}
return emptyList()
}
override suspend fun writeBle(ble: Ble) {
@ -381,6 +459,9 @@ class BleRepositoryImpl @Inject constructor(
request.saveHistory?.let { writeSaveEnabled(result.device, it) }
deviceCache.remove(serial)
resultList.remove(serial)
}
}
@ -457,24 +538,26 @@ class BleRepositoryImpl @Inject constructor(
private suspend fun writeSaveEnabled(
device: BluetoothDevice,
enabled: Boolean
) {
): Result<Unit, BleException> {
writeCharacteristic(
device = device,
serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"),
characteristicId = UUID.fromString("0000b6f2-0000-1000-8000-00805f9b34fb"),
writeData = mutableListOf<Byte>(3).apply {
writeData = mutableListOf<Byte>(4).apply {
add(if(enabled) 1 else 0)
}.toByteArray()
)
return Result.success(Unit)
}
private suspend fun readCharacteristic(
device: BluetoothDevice,
serviceId: UUID,
characteristicId: UUID
): ByteArray = suspendCoroutine {
): Result<ByteArray, BleException> = suspendCancellableCoroutine {
val callback = object : BluetoothGattCallback() {
@ -492,10 +575,10 @@ class BleRepositoryImpl @Inject constructor(
) == PackageManager.PERMISSION_GRANTED
) {
gatt?.discoverServices()
} else {
it.resume(Result.failure(BleException.PermissionDenied))
}
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
}
}
@ -512,16 +595,16 @@ class BleRepositoryImpl @Inject constructor(
service.uuid == serviceId
}?.characteristics?.firstOrNull { characteristic ->
characteristic.uuid == characteristicId
}?.let {
}?.let { char ->
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S || ActivityCompat.checkSelfPermission(
app,
Manifest.permission.BLUETOOTH_CONNECT
) == PackageManager.PERMISSION_GRANTED
) {
gatt.readCharacteristic(it)
gatt.readCharacteristic(char)
} else {
it.resume(Result.failure(BleException.PermissionDenied))
}
}
@ -537,14 +620,29 @@ class BleRepositoryImpl @Inject constructor(
status: Int
) {
super.onCharacteristicRead(gatt, characteristic, value, status)
it.resume(value)
if (ActivityCompat.checkSelfPermission(
app,
Manifest.permission.BLUETOOTH_CONNECT
) != PackageManager.PERMISSION_GRANTED
) {
it.resume(Result.failure(BleException.PermissionDenied))
}else {
gatt.disconnect()
it.resume(Result.success(value))
}
}
}
if (ActivityCompat.checkSelfPermission(
app,
Manifest.permission.BLUETOOTH_CONNECT
) != PackageManager.PERMISSION_GRANTED
) {
it.resume(Result.failure(BleException.PermissionDenied))
} else {
device.connectGatt(app, true, callback)
}
}
@ -553,7 +651,7 @@ class BleRepositoryImpl @Inject constructor(
serviceId: UUID,
characteristicId: UUID,
writeData: ByteArray
) = suspendCoroutine {
) = suspendCancellableCoroutine {
val callback = object : BluetoothGattCallback() {
@ -573,8 +671,6 @@ class BleRepositoryImpl @Inject constructor(
gatt?.discoverServices()
}
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
}
}
@ -591,7 +687,7 @@ class BleRepositoryImpl @Inject constructor(
service.uuid == serviceId
}?.characteristics?.firstOrNull { characteristic ->
characteristic.uuid == characteristicId
}?.let {
}?.let { char ->
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S || ActivityCompat.checkSelfPermission(
app,
@ -600,10 +696,10 @@ class BleRepositoryImpl @Inject constructor(
) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
gatt.writeCharacteristic(it, writeData, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
gatt.writeCharacteristic(char, writeData, BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
}else{
it.value = writeData
gatt.writeCharacteristic(it)
char.value = writeData
gatt.writeCharacteristic(char)
}
}
@ -620,11 +716,21 @@ class BleRepositoryImpl @Inject constructor(
status: Int
) {
super.onCharacteristicWrite(gatt, characteristic, status)
if (ActivityCompat.checkSelfPermission(
app,
Manifest.permission.BLUETOOTH_CONNECT
) != PackageManager.PERMISSION_GRANTED
) {
return
} else {
gatt.disconnect()
it.resume(Unit)
}
}
}
device.connectGatt(app, true, callback)
}

View File

@ -0,0 +1,7 @@
package llc.arma.ble.domain.common
sealed class BleException {
object PermissionDenied : BleException()
}

View File

@ -0,0 +1,15 @@
package llc.arma.ble.domain.common
sealed class ProgressState<out T> {
object Indeterminate : ProgressState<Nothing>()
data class Progress(
val value: Float
) : ProgressState<Nothing>()
data class Finished<T>(
val data: T
) : ProgressState<T>()
}

View File

@ -2,17 +2,19 @@ package llc.arma.ble.domain.repository
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.model.BleInfo
import llc.arma.ble.domain.usecase.GetBleBySerial
interface BleRepository {
fun getBleAroundFlow(): Flow<List<BleInfo>>
fun getBleAroundFlow(): Flow<Result<List<BleInfo>, BleException>>
suspend fun getBleBySerial(serial: String): Result<Ble, GetBleBySerial.GetBleException>
suspend fun getTemperatureHistoryBySerial(serial: String): List<Ble.Thermometer.MeasurePoint>
suspend fun getTemperatureHistoryBySerial(serial: String): Flow<Result<ProgressState<List<Ble.Thermometer.MeasurePoint>>, BleException>>
suspend fun writeBle(ble: Ble)

View File

@ -1,6 +1,8 @@
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.model.Ble
import llc.arma.ble.domain.model.BleInfo
import llc.arma.ble.domain.repository.BleRepository
@ -10,6 +12,6 @@ class GetBleAroundFlow @Inject constructor(
private val bleRepository: BleRepository
) {
operator fun invoke(): Flow<List<BleInfo>> = bleRepository.getBleAroundFlow()
operator fun invoke(): Flow<Result<List<BleInfo>, BleException>> = bleRepository.getBleAroundFlow()
}

View File

@ -1,5 +1,9 @@
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
@ -8,7 +12,7 @@ class GetTemperatureHistoryBySerial @Inject constructor(
private val bleRepository: BleRepository
) {
suspend operator fun invoke(serial: String): List<Ble.Thermometer.MeasurePoint> {
suspend operator fun invoke(serial: String): Flow<Result<ProgressState<List<Ble.Thermometer.MeasurePoint>>, BleException>> {
return bleRepository.getTemperatureHistoryBySerial(serial)

View File

@ -13,7 +13,10 @@ class WriteBle @Inject constructor(
bleRepository.writeBle(ble)
}
suspend operator fun invoke(serial: String, request: Ble.Thermometer.WriteRequest){
suspend operator fun invoke(
serial: String,
request: Ble.Thermometer.WriteRequest
){
bleRepository.writeBle(serial, request)
}