diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/beacon/BeaconScreen.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/beacon/BeaconScreen.kt index 3a51bdb..b705260 100644 --- a/app/src/main/java/llc/arma/ble/app/ui/screen/beacon/BeaconScreen.kt +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/beacon/BeaconScreen.kt @@ -21,6 +21,7 @@ import kotlinx.coroutines.launch import llc.arma.ble.app.ui.common.rememberBottomDialogState import llc.arma.ble.app.ui.screen.beacon.view.DisplayState import llc.arma.ble.app.ui.screen.beacon.view.PowerEdit +import llc.arma.ble.app.ui.screen.beacon.view.Write import llc.arma.ble.app.ui.screen.thermometer.localizedName import llc.arma.ble.domain.model.Ble @@ -75,256 +76,16 @@ fun BeaconScreen( when(sheetPage){ SheetPage.WRITE -> bottomDialog.show { - val scope = rememberCoroutineScope() - val currentState = viewModel.viewState.value - if(currentState is BeaconContract.State.Display) { + if(currentState is BeaconContract.State.Display && currentState.writeState != null) { - Column() { - - when (currentState.writeState) { - is BeaconContract.State.Display.WriteState.DisplayPreview -> { - - Text( - modifier = Modifier.padding(horizontal = 12.dp), - text = "Записать изменения?", - style = MaterialTheme.typography.titleLarge - ) - - currentState.writeState.writeRequest.tx?.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 = "${currentState.origin.state.tx.localizedName} db -> ${it.localizedName} db" - ) - - } - - } - - } - } - - Surface( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp) - .height(50.dp), - shape = CircleShape, - color = MaterialTheme.colorScheme.primaryContainer, - onClick = { - viewModel.setEvent(BeaconContract.Event.OnWriteBle) - } - ) { - - Box(modifier = Modifier.fillMaxSize()) { - - Text( - modifier = Modifier.align(Alignment.Center), - color = MaterialTheme.colorScheme.background, - style = MaterialTheme.typography.labelLarge, - text = "Записать" - ) - - } - - } - - Surface( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp) - .height(50.dp), - shape = CircleShape, - color = MaterialTheme.colorScheme.surfaceVariant, - onClick = { - scope.launch { - viewModel.setEvent(BeaconContract.Event.OnHideWriteBlePreview) - } - } - ) { - - Box(modifier = Modifier.fillMaxSize()) { - - Text( - modifier = Modifier.align(Alignment.Center), - color = MaterialTheme.colorScheme.onSurfaceVariant, - style = MaterialTheme.typography.labelLarge, - text = "Отменить" - ) - - } - - } - - - } - is BeaconContract.State.Display.WriteState.Writing -> { - - Box { - - Column() { - - Text( - modifier = Modifier.padding(horizontal = 12.dp), - text = "Запись", - style = MaterialTheme.typography.titleLarge - ) - - CircularProgressIndicator( - modifier = Modifier - .align(Alignment.CenterHorizontally) - .padding(bottom = 48.dp) - ) - - Surface( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp) - .height(50.dp), - shape = CircleShape, - color = MaterialTheme.colorScheme.surfaceVariant, - onClick = { - scope.launch { - viewModel.setEvent(BeaconContract.Event.OnHideWriteBlePreview) - } - } - ) { - - Box(modifier = Modifier.fillMaxSize()) { - - Text( - modifier = Modifier.align(Alignment.Center), - color = MaterialTheme.colorScheme.onSurfaceVariant, - style = MaterialTheme.typography.labelLarge, - text = "Отменить" - ) - - } - - } - - } - - } - - } - BeaconContract.State.Display.WriteState.Success -> { - - Box { - - Column { - - Text( - modifier = Modifier.padding(horizontal = 12.dp), - text = "Запись завершена", - style = MaterialTheme.typography.titleLarge - ) - - Surface( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp) - .height(50.dp), - shape = CircleShape, - color = MaterialTheme.colorScheme.primary, - onClick = { - scope.launch { - viewModel.setEvent(BeaconContract.Event.OnHideWriteBlePreview) - } - } - ) { - - Box(modifier = Modifier.fillMaxSize()) { - - Text( - modifier = Modifier.align(Alignment.Center), - color = MaterialTheme.colorScheme.onPrimary, - style = MaterialTheme.typography.labelLarge, - text = "Ок" - ) - - } - - } - - } - - } - - } - BeaconContract.State.Display.WriteState.Failure -> { - - Box { - - Column { - - Text( - modifier = Modifier.padding(horizontal = 12.dp), - text = "Ошибка записи", - style = MaterialTheme.typography.titleLarge - ) - - Surface( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp) - .height(50.dp), - shape = CircleShape, - color = MaterialTheme.colorScheme.primary, - onClick = { - scope.launch { - viewModel.setEvent(BeaconContract.Event.OnHideWriteBlePreview) - } - } - ) { - - Box(modifier = Modifier.fillMaxSize()) { - - Text( - modifier = Modifier.align(Alignment.Center), - color = MaterialTheme.colorScheme.onPrimary, - style = MaterialTheme.typography.labelLarge, - text = "Ок" - ) - - } - - } - - } - - } - - } - else -> {} + Write( + state = currentState.writeState, + onEvent = { + viewModel.setEvent(it) } - - Spacer(modifier = Modifier.height(48.dp)) - - } + ) } @@ -354,7 +115,8 @@ fun BeaconScreen( onEvent = { viewModel.setEvent(it) }, - ble = state.beacon + ble = state.beacon, + origin = state.origin ) is BeaconContract.State.Loading -> LoadingState() } diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/beacon/BeaconViewModel.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/beacon/BeaconViewModel.kt index bccebca..3131922 100644 --- a/app/src/main/java/llc/arma/ble/app/ui/screen/beacon/BeaconViewModel.kt +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/beacon/BeaconViewModel.kt @@ -84,13 +84,29 @@ class BeaconViewModel @Inject constructor( state: BeaconContract.State, event: BeaconContract.Event.OnBleChanged ) { - setState { - BeaconContract.State.Display( - origin = event.ble, - beacon = bleMapper.map(event.ble) as BleView.Beacon, - writeState = null - ) + + when(state){ + is BeaconContract.State.Display -> { + setState { + state.copy( + origin = Ble.Beacon( + info = event.ble.info, + state = state.origin.state + ) + ) + } + } + is BeaconContract.State.Loading -> { + setState { + BeaconContract.State.Display( + origin = event.ble, + beacon = bleMapper.map(event.ble) as BleView.Beacon, + writeState = null + ) + } + } } + } private fun reduce( @@ -147,34 +163,49 @@ class BeaconViewModel @Inject constructor( if(state is BeaconContract.State.Display){ - state.writeState?.let { + state.writeState?.let { request -> - if(it is BeaconContract.State.Display.WriteState.DisplayPreview) { + if(request is BeaconContract.State.Display.WriteState.DisplayPreview) { viewModelScope.launch { setState { state.copy( - writeState = BeaconContract.State.Display.WriteState.Writing(it.writeRequest) + writeState = BeaconContract.State.Display.WriteState.Writing(request.writeRequest) ) } - writeBle(state.beacon.info.serial, it.writeRequest).fold( - onSuccess = { - setState { - state.copy( - writeState = BeaconContract.State.Display.WriteState.Success - ) + val currentState = viewState.value + + if(currentState is BeaconContract.State.Display) { + + val newBleObject = Ble.Beacon( + info = currentState.origin.info, + state = currentState.origin.state.copy( + tx = request.writeRequest.tx ?: state.origin.state.tx + ) + ) + + writeBle(state.beacon.info.serial, request.writeRequest).fold( + onSuccess = { + setState { + currentState.copy( + origin = newBleObject, + beacon = bleMapper.map(newBleObject) as BleView.Beacon, + writeState = BeaconContract.State.Display.WriteState.Success + ) + } + }, + onFailure = { + setState { + state.copy( + writeState = BeaconContract.State.Display.WriteState.Failure + ) + } } - }, - onFailure = { - setState { - state.copy( - writeState = BeaconContract.State.Display.WriteState.Failure - ) - } - } - ) + ) + + } } diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/beacon/view/DisplayState.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/beacon/view/DisplayState.kt index b39bab8..fcfdeec 100644 --- a/app/src/main/java/llc/arma/ble/app/ui/screen/beacon/view/DisplayState.kt +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/beacon/view/DisplayState.kt @@ -18,10 +18,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.beacon.BeaconContract +import llc.arma.ble.domain.model.Ble @Composable fun DisplayState( onEvent: (BeaconContract.Event) -> Unit, + origin: Ble.Beacon, ble: BleView.Beacon ) { @@ -39,7 +41,7 @@ fun DisplayState( horizontal = 8.dp ) ) { - BleInfoView(bleInfo = ble.info) + BleInfoView(bleInfo = origin.info) } Column( diff --git a/app/src/main/java/llc/arma/ble/app/ui/screen/beacon/view/Write.kt b/app/src/main/java/llc/arma/ble/app/ui/screen/beacon/view/Write.kt new file mode 100644 index 0000000..2ca56dd --- /dev/null +++ b/app/src/main/java/llc/arma/ble/app/ui/screen/beacon/view/Write.kt @@ -0,0 +1,346 @@ +package llc.arma.ble.app.ui.screen.beacon.view + +import androidx.compose.animation.animateContentSize +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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 llc.arma.ble.R +import llc.arma.ble.app.ui.screen.beacon.BeaconContract +import llc.arma.ble.app.ui.screen.thermometer.localizedName + +@Composable +fun Write( + state: BeaconContract.State.Display.WriteState, + onEvent: (BeaconContract.Event) -> Unit +) { + + Column( + modifier = Modifier.animateContentSize() + ) { + + Text( + modifier = Modifier.padding(horizontal = 12.dp), + text = "Запись изменений", + style = MaterialTheme.typography.titleLarge + ) + + Spacer(modifier = Modifier.height(20.dp)) + + when (state) { + is BeaconContract.State.Display.WriteState.DisplayPreview -> { + + if(state.writeRequest.tx != null) { + + state.writeRequest.tx.let { + Box( + modifier = Modifier.padding( + vertical = 0.dp, + horizontal = 8.dp + ) + ) { + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .clip(RoundedCornerShape(16.dp)) + .padding(8.dp) + ) { + + Column( + modifier = Modifier.weight(1f) + ) { + + Text( + text = "Мощность" + ) + Text( + color = MaterialTheme.colorScheme.secondary, + style = MaterialTheme.typography.bodyMedium, + text = "${it.localizedName} db" + ) + + } + + } + + } + } + + Spacer(modifier = Modifier.height(20.dp)) + + Surface( + shape = CircleShape, + color = MaterialTheme.colorScheme.primaryContainer, + onClick = { + onEvent(BeaconContract.Event.OnWriteBle) + }, + modifier = Modifier + .fillMaxWidth() + .padding(8.dp) + .height(50.dp), + ) { + + Box(modifier = Modifier.fillMaxSize()) { + + Text( + modifier = Modifier.align(Alignment.Center), + color = MaterialTheme.colorScheme.background, + style = MaterialTheme.typography.labelLarge, + text = "Записать" + ) + + } + + } + + Surface( + shape = CircleShape, + color = MaterialTheme.colorScheme.surfaceVariant, + onClick = { + onEvent(BeaconContract.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 = "Отменить" + ) + + } + + } + + } else { + + 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(BeaconContract.Event.OnHideWriteBlePreview) + } + ) { + + Box(modifier = Modifier.fillMaxSize()) { + + Text( + modifier = Modifier.align(Alignment.Center), + color = MaterialTheme.colorScheme.onPrimary, + style = MaterialTheme.typography.labelLarge, + text = "Ок" + ) + + } + + } + + } + + + } + is BeaconContract.State.Display.WriteState.Writing -> { + + Box { + + Column() { + + Spacer(modifier = Modifier.height(28.dp)) + + CircularProgressIndicator( + strokeCap = StrokeCap.Round, + modifier = Modifier + .align(Alignment.CenterHorizontally) + ) + + Spacer(modifier = Modifier.height(48.dp)) + + Surface( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp) + .height(50.dp), + shape = CircleShape, + color = MaterialTheme.colorScheme.surfaceVariant, + onClick = { + onEvent(BeaconContract.Event.OnHideWriteBlePreview) + } + ) { + + Box(modifier = Modifier.fillMaxSize()) { + + Text( + modifier = Modifier.align(Alignment.Center), + color = MaterialTheme.colorScheme.onSurfaceVariant, + style = MaterialTheme.typography.labelLarge, + text = "Отменить" + ) + + } + + } + + } + + } + + } + BeaconContract.State.Display.WriteState.Success -> { + + Box { + + Column { + + Box( + modifier = Modifier + .padding(8.dp) + .fillMaxWidth() + ) { + + Image( + modifier = Modifier + .size(125.dp) + .align(Alignment.Center), + painter = painterResource(R.drawable.ic_done), + contentDescription = null + ) + + } + + Spacer(modifier = Modifier.height(16.dp)) + + Text( + modifier = Modifier.align(Alignment.CenterHorizontally), + text = "Успешно завершено" + ) + + Spacer(modifier = Modifier.height(20.dp)) + + Surface( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp) + .height(50.dp), + shape = CircleShape, + color = MaterialTheme.colorScheme.primary, + onClick = { + onEvent(BeaconContract.Event.OnHideWriteBlePreview) + } + ) { + + Box(modifier = Modifier.fillMaxSize()) { + + Text( + modifier = Modifier.align(Alignment.Center), + color = MaterialTheme.colorScheme.onPrimary, + style = MaterialTheme.typography.labelLarge, + text = "Ок" + ) + + } + + } + + } + + } + + } + BeaconContract.State.Display.WriteState.Failure -> { + + Box { + + Column { + + Box( + modifier = Modifier + .padding(8.dp) + .fillMaxWidth() + ) { + + Image( + modifier = Modifier + .size(125.dp) + .align(Alignment.Center), + painter = painterResource(R.drawable.ic_error), + contentDescription = null + ) + + } + + Spacer(modifier = Modifier.height(16.dp)) + + Text( + modifier = Modifier.align(Alignment.CenterHorizontally), + text = "Ошибка записи" + ) + + Spacer(modifier = Modifier.height(20.dp)) + + Surface( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp) + .height(50.dp), + shape = CircleShape, + color = MaterialTheme.colorScheme.primary, + onClick = { + onEvent(BeaconContract.Event.OnHideWriteBlePreview) + } + ) { + + Box(modifier = Modifier.fillMaxSize()) { + + Text( + modifier = Modifier.align(Alignment.Center), + color = MaterialTheme.colorScheme.onPrimary, + style = MaterialTheme.typography.labelLarge, + text = "Ок" + ) + + } + + } + + } + + } + + } + } + + } + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/data/BleRepositoryImpl.kt b/app/src/main/java/llc/arma/ble/data/BleRepositoryImpl.kt index b8e24a4..539ab11 100644 --- a/app/src/main/java/llc/arma/ble/data/BleRepositoryImpl.kt +++ b/app/src/main/java/llc/arma/ble/data/BleRepositoryImpl.kt @@ -231,7 +231,7 @@ class BleRepositoryImpl @Inject constructor( } - delay(500) + delay(1_000) } @@ -297,7 +297,7 @@ class BleRepositoryImpl @Inject constructor( } - delay(500) + delay(1_000) } @@ -310,7 +310,7 @@ class BleRepositoryImpl @Inject constructor( } - return llc.arma.ble.domain.Result.failure(BleException.UnexpectedResponse) + return Result.failure(BleException.UnexpectedResponse) } @@ -465,37 +465,6 @@ class BleRepositoryImpl @Inject constructor( } - /*request.tx?.let { - Log.d("write", "tx") - writeTx(result.device, it) - }?.onFailure { - Log.d("write", "tx fail") - return Result.failure(it) - } - - request.historyInterval?.let { - Log.d("write", "in") - writeSaveInterval(result.device, it) - }?.onFailure { - Log.d("write", "in fail") - return Result.failure(it) - } - - 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) - }*/ - } } @@ -503,44 +472,37 @@ class BleRepositoryImpl @Inject constructor( override suspend fun writeBle( serial: String, request: Ble.Beacon.WriteRequest - ): Result { + ): Result = suspendCancellableCoroutine { - deviceCache[serial]?.let { result -> + deviceCache[serial]?.let { scanResult -> + + if(checkPermission()) { + + var gatt: BluetoothGatt? = null + + val callback = WriteBeaconCallback(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 { writeTx(result.device, it) }?.onFailure { - return Result.failure(it) } - writeToFlash(serial).onFailure { - return Result.failure(it) - } - - deviceCache.remove(serial) - resultList.remove(serial) - } - return Result.success(Unit) - - } - - private suspend fun writeToFlash( - serial: String - ): Result{ - - deviceCache[serial]?.device?.let { result -> - - return writeCharacteristic( - device = result, - serviceId = serviceUUID, - characteristicId = flashWriteUUID, - writeData = byteArrayOf(9, 1) - ) - - } - - return Result.success(Unit) - } override suspend fun changeBlePassword( @@ -567,69 +529,6 @@ class BleRepositoryImpl @Inject constructor( return Result.success(Unit) } - private suspend fun writeTx( - device: BluetoothDevice, - tx: Ble.BleState.TX - ): Result { - - return writeCharacteristic( - device = device, - serviceId = serviceUUID, - characteristicId = txWriteUUID, - writeData = byteArrayOf( - when(tx) { - 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 - } - ) - ) - - } - - private suspend fun writeSaveInterval( - device: BluetoothDevice, - interval: Long - ): Result { - - fun UInt.to4ByteArrayInBigEndian(): ByteArray = - (3 downTo 0).map { - (this shr (it * Byte.SIZE_BITS)).toByte() - }.reversed().toByteArray() - - return writeCharacteristic( - device = device, - serviceId = serviceUUID, - characteristicId = intervalWriteUUID, - writeData = mutableListOf(3).apply { - addAll((interval / 1_000).toUInt().to4ByteArrayInBigEndian().toList()) - }.toByteArray() - ) - - } - - private suspend fun writeSaveEnabled( - device: BluetoothDevice, - enabled: Boolean - ): Result { - - return writeCharacteristic( - device = device, - serviceId = serviceUUID, - characteristicId = saveEnabledWriteUUID, - writeData = mutableListOf(4).apply { - add(if(enabled) 1 else 0) - }.toByteArray() - ) - - } - private suspend fun readCharacteristic( device: BluetoothDevice, serviceId: UUID, diff --git a/app/src/main/java/llc/arma/ble/data/WriteBeaconCallback.kt b/app/src/main/java/llc/arma/ble/data/WriteBeaconCallback.kt new file mode 100644 index 0000000..91a977a --- /dev/null +++ b/app/src/main/java/llc/arma/ble/data/WriteBeaconCallback.kt @@ -0,0 +1,212 @@ +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 WriteBeaconCallback( + private val app: Application, + private var request: Ble.Beacon.WriteRequest, + private val onResult: (Result) -> Unit +) : BluetoothGattCallback() { + + private var flashed = false + + override fun onConnectionStateChange( + gatt: BluetoothGatt, + status: Int, + newState: Int + ) { + super.onConnectionStateChange(gatt, status, newState) + + Log.d("beacon", "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("beacon", "onServicesDiscovered $status") + super.onServicesDiscovered(gatt, status) + onCycle(gatt, status) + + } + + private fun onCycle( + gatt: BluetoothGatt, + status: Int + ){ + + if(request.tx != null) { + + var uuid: Pair? = null + + 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("beacon", "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 { + + 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 + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/llc/arma/ble/domain/model/Ble.kt b/app/src/main/java/llc/arma/ble/domain/model/Ble.kt index 75a52ac..8a5f256 100644 --- a/app/src/main/java/llc/arma/ble/domain/model/Ble.kt +++ b/app/src/main/java/llc/arma/ble/domain/model/Ble.kt @@ -9,7 +9,7 @@ sealed class Ble( val state: BleState ) : Ble(info){ - class WriteRequest( + data class WriteRequest( val tx: BleState.TX? )