Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import java.util.Properties

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrainsKotlinAndroid)
alias(libs.plugins.google.devtools.ksp)
alias(libs.plugins.kotlinx.serialization)
alias(libs.plugins.google.protobuf)
Expand All @@ -19,8 +18,8 @@ android {
applicationId = "com.eva.bluetoothterminalapp"
minSdk = 29
targetSdk = 36
versionCode = 4
versionName = "1.2.0"
versionCode = 5
versionName = "1.2.1"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.eva.bluetoothterminalapp.domain.bluetooth.models.BluetoothDeviceModel
import com.eva.bluetoothterminalapp.domain.bluetooth_le.BluetoothLEClientConnector
import com.eva.bluetoothterminalapp.domain.bluetooth_le.enums.BLEConnectionState
import com.eva.bluetoothterminalapp.domain.bluetooth_le.models.BLECharacteristicsModel
import com.eva.bluetoothterminalapp.domain.bluetooth_le.models.BLEConnectionEvents
import com.eva.bluetoothterminalapp.domain.bluetooth_le.models.BLEDescriptorModel
import com.eva.bluetoothterminalapp.domain.bluetooth_le.models.BLEServiceModel
import com.eva.bluetoothterminalapp.domain.exceptions.BLEIndicationOrNotifyRunningException
Expand All @@ -28,6 +29,7 @@ import com.eva.bluetoothterminalapp.domain.exceptions.BluetoothNotEnabled
import com.eva.bluetoothterminalapp.domain.exceptions.BluetoothPermissionNotProvided
import com.eva.bluetoothterminalapp.domain.exceptions.InvalidBLEConfigurationException
import com.eva.bluetoothterminalapp.domain.exceptions.InvalidDeviceAddressException
import com.eva.bluetoothterminalapp.domain.exceptions.InvalidMTUValueException
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
Expand All @@ -52,8 +54,8 @@ class AndroidBLEClientConnector(
override val connectionState: StateFlow<BLEConnectionState>
get() = _gattCallback.connectionState

override val deviceRssi: StateFlow<Int>
get() = _gattCallback.deviceRssi
override val connEvents: Flow<BLEConnectionEvents>
get() = _gattCallback.connEvents

override val bleServices: Flow<List<BLEServiceModel>>
get() = _gattCallback.bleGattServices
Expand Down Expand Up @@ -131,6 +133,18 @@ class AndroidBLEClientConnector(
}
}

override fun onUpdateMTU(mtu: Int): Result<Boolean> {
return try {
if (mtu !in 23..517) return Result.failure(InvalidMTUValueException())
Log.i(TAG, "REQUESTING MTU UPDATE")
val result = _bLEGatt?.requestMtu(mtu) ?: false
Result.success(result)
} catch (e: Exception) {
Log.d(TAG, "FAILED TO UPDATE MTU", e)
Result.failure(e)
}
}

override fun read(service: BLEServiceModel, characteristic: BLECharacteristicsModel)
: Result<Boolean> {
return try {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.eva.bluetoothterminalapp.data.bluetooth_le

import android.annotation.SuppressLint
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic
Expand All @@ -13,15 +14,20 @@ import com.eva.bluetoothterminalapp.data.mapper.toDomainModelWithName
import com.eva.bluetoothterminalapp.data.mapper.toDomainModelWithNames
import com.eva.bluetoothterminalapp.data.samples.SampleUUIDReader
import com.eva.bluetoothterminalapp.domain.bluetooth_le.enums.BLEConnectionState
import com.eva.bluetoothterminalapp.domain.bluetooth_le.enums.BLEPhysicalChannels
import com.eva.bluetoothterminalapp.domain.bluetooth_le.models.BLECharacteristicsModel
import com.eva.bluetoothterminalapp.domain.bluetooth_le.models.BLEConnectionEvents
import com.eva.bluetoothterminalapp.domain.bluetooth_le.models.BLEDescriptorModel
import com.eva.bluetoothterminalapp.domain.bluetooth_le.models.BLEServiceModel
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
Expand All @@ -38,9 +44,6 @@ class BLEClientGattCallback(

private val scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())

private val _deviceRssi = MutableStateFlow(0)
val deviceRssi = _deviceRssi.asStateFlow()

private val _connectionState = MutableStateFlow(BLEConnectionState.CONNECTING)
val connectionState = _connectionState.asStateFlow()

Expand All @@ -54,11 +57,18 @@ class BLEClientGattCallback(
private val _readCharacteristic = MutableStateFlow<BLECharacteristicsModel?>(null)
val readCharacteristics = _readCharacteristic.asStateFlow()

override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
private val _events = MutableSharedFlow<BLEConnectionEvents>(
replay = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
val connEvents = _events.asSharedFlow()

override fun onConnectionStateChange(gatt: BluetoothGatt?, status: Int, newState: Int) {
if (status != BluetoothGatt.GATT_SUCCESS) {
_connectionState.update { BLEConnectionState.FAILED }
Log.e(GATT_LOGGER, "PROBLEM WITH CONNECTION STATUS CODE: $status")
Log.d(GATT_LOGGER, "CLOSING CONNECTION")
gatt?.close()
return
}

Expand Down Expand Up @@ -88,7 +98,26 @@ class BLEClientGattCallback(
if (status != BluetoothGatt.GATT_SUCCESS) return
Log.d(GATT_LOGGER, "READING RSSI SUCCESSFUL")
// update rssi value
_deviceRssi.update { rssi }
_events.tryEmit(BLEConnectionEvents.OnRSSIUpdated(rssi))
}

override fun onMtuChanged(gatt: BluetoothGatt?, mtu: Int, status: Int) {
if (status != BluetoothGatt.GATT_SUCCESS) return
Log.d(GATT_LOGGER, "UPDATING MTU SUCCESSFUL :$mtu")
_events.tryEmit(BLEConnectionEvents.OnMTUUpdated(mtu))
}

override fun onPhyRead(gatt: BluetoothGatt?, txPhy: Int, rxPhy: Int, status: Int) {
if (status != BluetoothGatt.GATT_SUCCESS) return
Log.d(GATT_LOGGER, "PHY READ TX:$txPhy RX:$rxPhy")
}

override fun onPhyUpdate(gatt: BluetoothGatt?, txPhy: Int, rxPhy: Int, status: Int) {
if (status != BluetoothGatt.GATT_SUCCESS) return
val txChannel = txPhy.toBLPhy()
val rxChannel = rxPhy.toBLPhy()
Log.d(GATT_LOGGER, "PHY READ UPDATED TX:$txChannel RX:$rxChannel")
_events.tryEmit(BLEConnectionEvents.OnPhyUpdated(txChannel, rxChannel))
}

override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
Expand Down Expand Up @@ -274,7 +303,9 @@ class BLEClientGattCallback(
}
}

fun cleanUp() = scope.cancel()
fun cleanUp() {
scope.cancel()
}

fun findCharacteristicFromDomainModel(
service: BLEServiceModel,
Expand All @@ -297,4 +328,11 @@ class BLEClientGattCallback(
?.getDescriptor(descriptor.uuid)
}


private fun Int.toBLPhy() = when (this) {
BluetoothDevice.PHY_LE_CODED -> BLEPhysicalChannels.LE_CODED
BluetoothDevice.PHY_LE_1M -> BLEPhysicalChannels.LE_1M
BluetoothDevice.PHY_LE_2M -> BLEPhysicalChannels.LE_2M
else -> throw IllegalArgumentException("Invalid transmission value")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
import kotlin.time.Duration.Companion.seconds
import kotlin.time.DurationUnit

Expand All @@ -36,11 +37,11 @@ class LightSensorReaderImpl(private val context: Context) : LightSensorReader {
@OptIn(ExperimentalCoroutinesApi::class)
override suspend fun readCurrentValue(): Float? = suspendCancellableCoroutine { cont ->
if (!_isSensorAvailable) {
cont.resume(null, onCancellation = {})
cont.resume(null)
return@suspendCancellableCoroutine
}
val sensor = _lightSensor ?: run {
cont.resume(null, onCancellation = {})
cont.resume(null)
return@suspendCancellableCoroutine
}

Expand All @@ -49,7 +50,7 @@ class LightSensorReaderImpl(private val context: Context) : LightSensorReader {
override fun onSensorChanged(event: SensorEvent?) {
val lux = event?.values?.firstOrNull()
if (cont.isActive) {
cont.resume(lux, onCancellation = {})
cont.resume(lux)
_sensorManager?.unregisterListener(this)
Log.d(TAG, "LIGHT SENSOR LISTENER UN-REGISTERED")
}
Expand All @@ -62,7 +63,7 @@ class LightSensorReaderImpl(private val context: Context) : LightSensorReader {

if (!registered) {
Log.e(TAG, "CANNOT REGISTER FOR SENSOR")
cont.resume(null, onCancellation = null)
cont.resume(null)
return@suspendCancellableCoroutine
}
Log.d(TAG, "LIGHT SENSOR LISTENER REGISTERED")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.eva.bluetoothterminalapp.domain.bluetooth.models.BluetoothDeviceModel
import com.eva.bluetoothterminalapp.domain.bluetooth_le.enums.BLEConnectionState
import com.eva.bluetoothterminalapp.domain.bluetooth_le.enums.BLEPropertyTypes
import com.eva.bluetoothterminalapp.domain.bluetooth_le.models.BLECharacteristicsModel
import com.eva.bluetoothterminalapp.domain.bluetooth_le.models.BLEConnectionEvents
import com.eva.bluetoothterminalapp.domain.bluetooth_le.models.BLEDescriptorModel
import com.eva.bluetoothterminalapp.domain.bluetooth_le.models.BLEServiceModel
import kotlinx.coroutines.flow.Flow
Expand All @@ -20,7 +21,7 @@ interface BluetoothLEClientConnector {
/**
* Receive signal strength of the device
*/
val deviceRssi: StateFlow<Int>
val connEvents: Flow<BLEConnectionEvents>

/**
* Available [BLEServiceModel] with the current selected service
Expand Down Expand Up @@ -117,6 +118,8 @@ interface BluetoothLEClientConnector {
): Result<Boolean>


fun onUpdateMTU(mtu: Int): Result<Boolean>

/**
* Rediscover available services for the device
* @return [Result] If the operation done correctly
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.eva.bluetoothterminalapp.domain.bluetooth_le.enums

enum class BLEPhysicalChannels {
LE_1M,
LE_2M,
LE_CODED,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.eva.bluetoothterminalapp.domain.bluetooth_le.models

import com.eva.bluetoothterminalapp.domain.bluetooth_le.enums.BLEPhysicalChannels

sealed class BLEConnectionEvents {
data class OnRSSIUpdated(val rssi: Int) : BLEConnectionEvents()
data class OnMTUUpdated(val mtu: Int) : BLEConnectionEvents()
data class OnPhyUpdated(val tx: BLEPhysicalChannels, val rx: BLEPhysicalChannels) :
BLEConnectionEvents()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.eva.bluetoothterminalapp.domain.exceptions

class InvalidMTUValueException : Exception("Invalid value for mtu cannot be larger than 517")
Loading