Fix bug GATT_FAILURE

This commit is contained in:
Vineyro 2023-03-28 12:13:05 +07:00
parent ce95316494
commit 0d7019a7be
3 changed files with 263 additions and 102 deletions

View File

@ -37,6 +37,7 @@ import kotlin.random.nextInt
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Refresh import androidx.compose.material.icons.rounded.Refresh
import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.text.style.TextAlign
import com.patrykandpatrick.vico.compose.chart.scroll.rememberChartScrollState import com.patrykandpatrick.vico.compose.chart.scroll.rememberChartScrollState
import com.patrykandpatrick.vico.core.axis.AxisPosition import com.patrykandpatrick.vico.core.axis.AxisPosition
import com.patrykandpatrick.vico.core.axis.formatter.AxisValueFormatter import com.patrykandpatrick.vico.core.axis.formatter.AxisValueFormatter
@ -73,7 +74,7 @@ fun TemperatureHistory(
viewModel.setEvent(TemperatureHistoryContract.Event.LoadHistory(ble.serial)) viewModel.setEvent(TemperatureHistoryContract.Event.LoadHistory(ble.serial))
} }
Column() { Column {
Row( Row(
modifier = Modifier.padding(horizontal = 12.dp), modifier = Modifier.padding(horizontal = 12.dp),
@ -90,7 +91,10 @@ fun TemperatureHistory(
onClick = { onClick = {
viewModel.setEvent(TemperatureHistoryContract.Event.LoadHistory(ble.serial)) viewModel.setEvent(TemperatureHistoryContract.Event.LoadHistory(ble.serial))
}, },
enabled = state.loadingHistoryState is ProgressState.Finished enabled = when(state){
is TemperatureHistoryContract.State.Display -> state.loadingHistoryState is ProgressState.Finished
TemperatureHistoryContract.State.Exception -> true
}
) { ) {
Icon( Icon(
imageVector = Icons.Rounded.Refresh, imageVector = Icons.Rounded.Refresh,
@ -102,6 +106,19 @@ fun TemperatureHistory(
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
when(state){
is TemperatureHistoryContract.State.Display -> Display(state = state)
TemperatureHistoryContract.State.Exception -> Exception()
}
}
}
@Composable
fun Display(
state: TemperatureHistoryContract.State.Display
) {
when (state.loadingHistoryState) { when (state.loadingHistoryState) {
is ProgressState.Finished -> { is ProgressState.Finished -> {
@ -190,6 +207,24 @@ fun TemperatureHistory(
} }
} }
}
@Composable
fun Exception(
) {
Box(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
.aspectRatio(2f),
){
Text(
textAlign = TextAlign.Center,
text = "Во время загрузки произошла ошибка",
modifier = Modifier.align(Alignment.Center)
)
} }
@ -205,9 +240,15 @@ class TemperatureHistoryContract {
} }
class State( sealed class State : ViewState {
data class Display(
val loadingHistoryState : ProgressState<List<Ble.Thermometer.MeasurePoint>> val loadingHistoryState : ProgressState<List<Ble.Thermometer.MeasurePoint>>
) : ViewState ) : State()
object Exception : State()
}
sealed class Effect : ViewSideEffect { sealed class Effect : ViewSideEffect {
@ -222,7 +263,7 @@ class TemperatureHistoryViewModel @Inject constructor(
private val getTemperatureHistoryBySerial: GetTemperatureHistoryBySerial private val getTemperatureHistoryBySerial: GetTemperatureHistoryBySerial
) : BaseViewModel<TemperatureHistoryContract.State, TemperatureHistoryContract.Event, TemperatureHistoryContract.Effect>() { ) : BaseViewModel<TemperatureHistoryContract.State, TemperatureHistoryContract.Event, TemperatureHistoryContract.Effect>() {
override fun setInitialState() = TemperatureHistoryContract.State( override fun setInitialState() = TemperatureHistoryContract.State.Display(
ProgressState.Indeterminate ProgressState.Indeterminate
) )
@ -239,18 +280,20 @@ class TemperatureHistoryViewModel @Inject constructor(
viewModelScope.launch { viewModelScope.launch {
setState { setState {
TemperatureHistoryContract.State(ProgressState.Indeterminate) TemperatureHistoryContract.State.Display(ProgressState.Indeterminate)
} }
getTemperatureHistoryBySerial(event.serial).onEach { getTemperatureHistoryBySerial(event.serial).onEach {
it.fold( it.fold(
onSuccess = { onSuccess = {
setState { setState {
TemperatureHistoryContract.State(it) TemperatureHistoryContract.State.Display(it)
} }
}, },
onFailure = { onFailure = {
setState {
TemperatureHistoryContract.State.Exception
}
} }
) )
}.launchIn(this) }.launchIn(this)

View File

@ -4,10 +4,12 @@ import android.Manifest
import android.app.Application import android.app.Application
import android.bluetooth.* import android.bluetooth.*
import android.bluetooth.le.ScanCallback import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanFilter
import android.bluetooth.le.ScanResult import android.bluetooth.le.ScanResult
import android.bluetooth.le.ScanSettings import android.bluetooth.le.ScanSettings
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build import android.os.Build
import android.util.Log
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
@ -25,6 +27,7 @@ import java.util.*
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
@Singleton @Singleton
class BleRepositoryImpl @Inject constructor( class BleRepositoryImpl @Inject constructor(
@ -316,7 +319,15 @@ class BleRepositoryImpl @Inject constructor(
((this[idx + 1].toUInt() and 0xFFu) shl 8) or ((this[idx + 1].toUInt() and 0xFFu) shl 8) or
(this[idx].toUInt() and 0xFFu) (this[idx].toUInt() and 0xFFu)
deviceCache[serial]?.device?.let { device -> findDeviceBySerial(serial).fold(
onSuccess = {
return@fold it
},
onFailure = {
emit(Result.failure(it))
return@flow
}
).let { device ->
emit(Result.success(ProgressState.Indeterminate)) emit(Result.success(ProgressState.Indeterminate))
@ -330,7 +341,7 @@ class BleRepositoryImpl @Inject constructor(
val countDataArray = readCharacteristic( val countDataArray = readCharacteristic(
device = device, device = device,
serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"), serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"),
characteristicId = UUID.fromString("0000b2d8-0000-1000-8000-00805f9b34fb"), characteristicId = UUID.fromString("0000b2d8-0000-1000-8000-00805f9b34fb")
).fold( ).fold(
onFailure = { onFailure = {
emit(Result.failure(it)) emit(Result.failure(it))
@ -355,7 +366,7 @@ class BleRepositoryImpl @Inject constructor(
val firstPackageResponse = readCharacteristic( val firstPackageResponse = readCharacteristic(
device = device, device = device,
serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"), serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"),
characteristicId = UUID.fromString("0000b2d8-0000-1000-8000-00805f9b34fb"), characteristicId = UUID.fromString("0000b2d8-0000-1000-8000-00805f9b34fb")
).fold( ).fold(
onFailure = { onFailure = {
emit(Result.failure(it)) emit(Result.failure(it))
@ -397,7 +408,7 @@ class BleRepositoryImpl @Inject constructor(
val readResponse = readCharacteristic( val readResponse = readCharacteristic(
device = device, device = device,
serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"), serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"),
characteristicId = UUID.fromString("0000b2d8-0000-1000-8000-00805f9b34fb"), characteristicId = UUID.fromString("0000b2d8-0000-1000-8000-00805f9b34fb")
).fold( ).fold(
onFailure = { onFailure = {
emit(Result.failure(it)) emit(Result.failure(it))
@ -406,6 +417,8 @@ class BleRepositoryImpl @Inject constructor(
onSuccess = { return@fold it } onSuccess = { return@fold it }
) )
if(readResponse[0] == 251.toByte()) {
dataCount = readResponse[1].toUByte() dataCount = readResponse[1].toUByte()
temperatureDataArray = readResponse.toList().subList(2, readResponse.size) temperatureDataArray = readResponse.toList().subList(2, readResponse.size)
@ -418,8 +431,20 @@ class BleRepositoryImpl @Inject constructor(
emit(Result.success(ProgressState.Progress(totalDataSize.toFloat() / temperaturePackage.size.toFloat()))) emit(Result.success(ProgressState.Progress(totalDataSize.toFloat() / temperaturePackage.size.toFloat())))
} else {
emit(Result.failure(BleException.UnexpectedResponse))
} }
}
readCharacteristic(
device = device,
serviceId = UUID.fromString("a77db03a-9bc4-11ed-a8fc-0242ac120002"),
characteristicId = UUID.fromString("0000b2d8-0000-1000-8000-00805f9b34fb")
)
emit( emit(
Result.success( Result.success(
ProgressState.Finished( ProgressState.Finished(
@ -433,6 +458,10 @@ class BleRepositoryImpl @Inject constructor(
) )
) )
} else {
emit(Result.failure(BleException.UnexpectedResponse))
} }
} }
@ -559,14 +588,19 @@ class BleRepositoryImpl @Inject constructor(
characteristicId: UUID characteristicId: UUID
): Result<ByteArray, BleException> = suspendCancellableCoroutine { ): Result<ByteArray, BleException> = suspendCancellableCoroutine {
var result: ByteArray? = null
var bleGatt: BluetoothGatt? = null
val callback = object : BluetoothGattCallback() { val callback = object : BluetoothGattCallback() {
override fun onConnectionStateChange( override fun onConnectionStateChange(
gatt: BluetoothGatt?, gatt: BluetoothGatt,
status: Int, status: Int,
newState: Int newState: Int
) { ) {
Log.d("read", "onConnectionStateChange $newState $status")
if (newState == BluetoothProfile.STATE_CONNECTED) { if (newState == BluetoothProfile.STATE_CONNECTED) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S || ActivityCompat.checkSelfPermission( if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S || ActivityCompat.checkSelfPermission(
@ -574,7 +608,7 @@ class BleRepositoryImpl @Inject constructor(
Manifest.permission.BLUETOOTH_CONNECT Manifest.permission.BLUETOOTH_CONNECT
) == PackageManager.PERMISSION_GRANTED ) == PackageManager.PERMISSION_GRANTED
) { ) {
gatt?.discoverServices() gatt.discoverServices()
} else { } else {
it.resume(Result.failure(BleException.PermissionDenied)) it.resume(Result.failure(BleException.PermissionDenied))
} }
@ -584,14 +618,16 @@ class BleRepositoryImpl @Inject constructor(
} }
override fun onServicesDiscovered( override fun onServicesDiscovered(
gatt: BluetoothGatt?, gatt: BluetoothGatt,
status: Int status: Int
) { ) {
super.onServicesDiscovered(gatt, status) super.onServicesDiscovered(gatt, status)
Log.d("read", "onServicesDiscovered $status")
if (status == BluetoothGatt.GATT_SUCCESS) { if (status == BluetoothGatt.GATT_SUCCESS) {
gatt?.services?.firstOrNull { service -> gatt.services?.firstOrNull { service ->
service.uuid == serviceId service.uuid == serviceId
}?.characteristics?.firstOrNull { characteristic -> }?.characteristics?.firstOrNull { characteristic ->
characteristic.uuid == characteristicId characteristic.uuid == characteristicId
@ -620,6 +656,9 @@ class BleRepositoryImpl @Inject constructor(
status: Int status: Int
) { ) {
super.onCharacteristicRead(gatt, characteristic, value, status) super.onCharacteristicRead(gatt, characteristic, value, status)
Log.d("read", "onCharacteristicRead $status")
if (ActivityCompat.checkSelfPermission( if (ActivityCompat.checkSelfPermission(
app, app,
Manifest.permission.BLUETOOTH_CONNECT Manifest.permission.BLUETOOTH_CONNECT
@ -627,9 +666,17 @@ class BleRepositoryImpl @Inject constructor(
) { ) {
it.resume(Result.failure(BleException.PermissionDenied)) it.resume(Result.failure(BleException.PermissionDenied))
}else { }else {
gatt.disconnect() gatt.close()
it.resume(Result.success(value)) result = value
if(result != null){
it.resume(Result.success(result!!))
} else {
bleGatt?.close()
it.resume(Result.failure(BleException.UnexpectedResponse))
} }
}
} }
} }
@ -641,7 +688,7 @@ class BleRepositoryImpl @Inject constructor(
) { ) {
it.resume(Result.failure(BleException.PermissionDenied)) it.resume(Result.failure(BleException.PermissionDenied))
} else { } else {
device.connectGatt(app, true, callback) bleGatt = device.connectGatt(app, true, callback)
} }
} }
@ -653,14 +700,18 @@ class BleRepositoryImpl @Inject constructor(
writeData: ByteArray writeData: ByteArray
) = suspendCancellableCoroutine { ) = suspendCancellableCoroutine {
var bleGatt: BluetoothGatt? = null
val callback = object : BluetoothGattCallback() { val callback = object : BluetoothGattCallback() {
override fun onConnectionStateChange( override fun onConnectionStateChange(
gatt: BluetoothGatt?, gatt: BluetoothGatt,
status: Int, status: Int,
newState: Int newState: Int
) { ) {
Log.d("write", "onConnectionStateChange $newState")
if (newState == BluetoothProfile.STATE_CONNECTED) { if (newState == BluetoothProfile.STATE_CONNECTED) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S || ActivityCompat.checkSelfPermission( if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S || ActivityCompat.checkSelfPermission(
@ -668,7 +719,13 @@ class BleRepositoryImpl @Inject constructor(
Manifest.permission.BLUETOOTH_CONNECT Manifest.permission.BLUETOOTH_CONNECT
) == PackageManager.PERMISSION_GRANTED ) == PackageManager.PERMISSION_GRANTED
) { ) {
gatt?.discoverServices() gatt.discoverServices()
}
} else {
if(newState == BluetoothProfile.STATE_DISCONNECTED && status == BluetoothGatt.GATT_FAILURE){
bleGatt?.close()
} }
} }
@ -676,14 +733,14 @@ class BleRepositoryImpl @Inject constructor(
} }
override fun onServicesDiscovered( override fun onServicesDiscovered(
gatt: BluetoothGatt?, gatt: BluetoothGatt,
status: Int status: Int
) { ) {
super.onServicesDiscovered(gatt, status) super.onServicesDiscovered(gatt, status)
Log.d("write", "onServicesDiscovered $status")
if (status == BluetoothGatt.GATT_SUCCESS) { if (status == BluetoothGatt.GATT_SUCCESS) {
gatt?.services?.firstOrNull { service -> gatt.services?.firstOrNull { service ->
service.uuid == serviceId service.uuid == serviceId
}?.characteristics?.firstOrNull { characteristic -> }?.characteristics?.firstOrNull { characteristic ->
characteristic.uuid == characteristicId characteristic.uuid == characteristicId
@ -712,10 +769,13 @@ class BleRepositoryImpl @Inject constructor(
override fun onCharacteristicWrite( override fun onCharacteristicWrite(
gatt: BluetoothGatt, gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic?, characteristic: BluetoothGattCharacteristic,
status: Int status: Int
) { ) {
super.onCharacteristicWrite(gatt, characteristic, status) super.onCharacteristicWrite(gatt, characteristic, status)
Log.d("write", "onCharacteristicWrite $status")
if (ActivityCompat.checkSelfPermission( if (ActivityCompat.checkSelfPermission(
app, app,
Manifest.permission.BLUETOOTH_CONNECT Manifest.permission.BLUETOOTH_CONNECT
@ -723,7 +783,7 @@ class BleRepositoryImpl @Inject constructor(
) { ) {
return return
} else { } else {
gatt.disconnect() gatt.close()
it.resume(Unit) it.resume(Unit)
} }
@ -731,7 +791,63 @@ class BleRepositoryImpl @Inject constructor(
} }
device.connectGatt(app, true, callback) bleGatt = device.connectGatt(app, true, callback)
}
private suspend fun findDeviceBySerial(serial: String): Result<BluetoothDevice, BleException> = suspendCancellableCoroutine {
val bleCallback = object : ScanCallback() {
override fun onScanResult(
callbackType: Int,
result: ScanResult
) {
super.onScanResult(callbackType, result)
if(it.isActive) {
if (ActivityCompat.checkSelfPermission(
app,
Manifest.permission.BLUETOOTH_CONNECT
) == PackageManager.PERMISSION_GRANTED
) {
it.resume(Result.success(result.device))
} else {
CoroutineScope(Dispatchers.IO).launch {
it.resume(
Result.failure(BleException.PermissionDenied)
)
}
}
}
}
}
val bleScanner =
app.getSystemService(BluetoothManager::class.java).adapter.bluetoothLeScanner
bleScanner.startScan(
listOf(ScanFilter.Builder().setDeviceAddress(serial).build()),
ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
.setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH)
.setMatchMode(ScanSettings.MATCH_MODE_AGGRESSIVE)
.setNumOfMatches(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT)
.setReportDelay(400L)
.build(),
bleCallback)
it.invokeOnCancellation {
bleScanner.stopScan(bleCallback)
}
} }

View File

@ -4,4 +4,6 @@ sealed class BleException {
object PermissionDenied : BleException() object PermissionDenied : BleException()
object UnexpectedResponse : BleException()
} }