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
12 changes: 10 additions & 2 deletions app/src/main/java/com/wifi/improv/demo/ImprovViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class ImprovViewModel : ViewModel(), ImprovManagerCallback {
var connectedDevice: ImprovDevice? = null
var deviceState: DeviceState? = null
var errorState: ErrorState? = null
var rpcResult = listOf<String>()

private fun update() {
viewModelScope.launch {
Expand All @@ -31,7 +32,8 @@ class ImprovViewModel : ViewModel(), ImprovManagerCallback {
connectedDevice?.address ?: "",
connectedDevice != null,
deviceState.toString(),
errorState.toString()
errorState.toString(),
rpcResult
)
}
}
Expand Down Expand Up @@ -64,6 +66,11 @@ class ImprovViewModel : ViewModel(), ImprovManagerCallback {
this.errorState = errorState
update()
}

override fun onRpcResult(result: List<String>) {
this.rpcResult = result
update()
}
}

@Immutable
Expand All @@ -74,5 +81,6 @@ data class ImprovScreenState(
val address: String,
val btConnected: Boolean,
val deviceState: String,
val errorState: String
val errorState: String,
val rpcResult: List<String>
)
10 changes: 7 additions & 3 deletions app/src/main/java/com/wifi/improv/demo/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ fun ImprovMain(
name = screenState.name,
address = screenState.address,
deviceState = screenState.deviceState,
errorState = screenState.errorState
errorState = screenState.errorState,
rpcResult = screenState.rpcResult
)
Spacer(Modifier.padding(5.dp))
}
Expand All @@ -132,7 +133,8 @@ fun ImprovStatus(
name: String?,
address: String?,
deviceState: String?,
errorState: String?
errorState: String?,
rpcResult: List<String>
) {
Card(modifier = Modifier.fillMaxWidth()) {
Column {
Expand All @@ -144,6 +146,7 @@ fun ImprovStatus(
Text(text = "Address: $address")
Text(text = "Device State: $deviceState")
Text(text = "Error State: $errorState")
Text(text = "RPC Result: $rpcResult")
}
}
}
Expand Down Expand Up @@ -220,7 +223,8 @@ fun DefaultPreview() {
"12:34:56:78",
true,
DeviceState.AUTHORIZED.toString(),
"errorState"
"errorState",
listOf()
)
ImprovMain(screenState = improvScreenState, {}, {}, {}, { _, _ -> })
}
3 changes: 3 additions & 0 deletions library/src/main/java/com/wifi/improv/BluetoothOperations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ sealed class BleOperationType
data class Connect(val device: BluetoothDevice) : BleOperationType()
data class Disconnect(val device: BluetoothDevice) : BleOperationType()

object DiscoverServices: BleOperationType()
object RequestLargeMtu: BleOperationType()

data class CharacteristicRead(val char: BluetoothGattCharacteristic) : BleOperationType()
data class CharacteristicWrite(val char: BluetoothGattCharacteristic) : BleOperationType()

Expand Down
86 changes: 81 additions & 5 deletions library/src/main/java/com/wifi/improv/ImprovManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ class ImprovManager(
UUID.fromString("00467768-6228-2272-4663-277478268001")
private val UUID_CHAR_ERROR_STATE: UUID =
UUID.fromString("00467768-6228-2272-4663-277478268002")
private val UUID_CHAR_RPC: UUID = UUID.fromString("00467768-6228-2272-4663-277478268003")
private val UUID_CHAR_RPC: UUID =
UUID.fromString("00467768-6228-2272-4663-277478268003")
private val UUID_CHAR_RPC_RESULT: UUID =
UUID.fromString("00467768-6228-2272-4663-277478268004")
}

private val bluetoothManager: BluetoothManager =
Expand Down Expand Up @@ -63,8 +66,8 @@ class ImprovManager(
)
)

gatt.discoverServices()

operationQueue.add(RequestLargeMtu)
operationQueue.add(DiscoverServices)
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.w(TAG, "Successfully disconnected from $deviceAddress")
gatt.close()
Expand All @@ -86,10 +89,10 @@ class ImprovManager(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic
) {
val value =
characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0).toUByte()
when (characteristic.uuid) {
UUID_CHAR_CURRENT_STATE -> {
val value =
characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0).toUByte()
Log.i(TAG, "Current State has changed to $value.")
val deviceState = DeviceState.values().firstOrNull { it.value == value }
if (deviceState != null)
Expand All @@ -98,13 +101,23 @@ class ImprovManager(
Log.e(TAG, "Unable to determine Current State")
}
UUID_CHAR_ERROR_STATE -> {
val value =
characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0).toUByte()
Log.i(TAG, "Error State has changed to $value.")
val errorState = ErrorState.values().firstOrNull { it.value == value }
if (errorState != null)
callback.onErrorStateChange(errorState)
else
Log.e(TAG, "Unable to determine Error State")
}
UUID_CHAR_RPC_RESULT -> {
Log.i(TAG, "RPC Result has changed to ${characteristic.value.joinToString()}.")
val result = extractResultStrings(characteristic.value)
if (result != null)
callback.onRpcResult(result)
else
Log.w(TAG, "Received empty RPC Result")
}
}
}

Expand Down Expand Up @@ -187,6 +200,26 @@ class ImprovManager(
}
} else
Log.e(TAG, "Unable to register for Error State Notifications")

val rpcResultChar = service.getCharacteristic(UUID_CHAR_RPC_RESULT)
enqueueOperation(CharacteristicRead(rpcResultChar))
if (gatt.setCharacteristicNotification(rpcResultChar, true)) {
Log.i(TAG, "Registered for RPC Result Notifications")
rpcResultChar.descriptors.firstOrNull()?.let {
it.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
enqueueOperation(DescriptorWrite(it))
}
} else
Log.e(TAG, "Unable to register for RPC Result Notifications")

if (pendingOperation is DiscoverServices)
signalEndOfOperation()
}

override fun onMtuChanged(gatt: BluetoothGatt?, mtu: Int, status: Int) {
Log.d(TAG, "MTU change to $mtu returned status: $status")
if (pendingOperation is RequestLargeMtu)
signalEndOfOperation()
}
}

Expand Down Expand Up @@ -257,6 +290,35 @@ class ImprovManager(
enqueueOperation(CharacteristicWrite(rpc))
}

private fun extractResultStrings(data: ByteArray): List<String>? {
// Ensure the data is at least 3 bytes long to read the first string length
if (data.size < 3) return null

val strings = mutableListOf<String>()
var currentIndex = 2 // Start after the first two bytes

while (currentIndex < data.size) {
// Get the length of the current string
val stringLength = data[currentIndex].toInt()
currentIndex++

// Ensure there are enough bytes left for the current string
if (currentIndex + stringLength > data.size) return strings

// Extract the string and add it to the list
try {
val string = data.decodeToString(currentIndex, currentIndex + stringLength, throwOnInvalidSequence = true)
currentIndex += stringLength
strings += string
} catch (e: Exception) {
Log.e(TAG, "Invalid string encoding, returning strings previously decoded")
return strings
}
}

return strings
}

private val operationQueue = ConcurrentLinkedQueue<BleOperationType>()
private var pendingOperation: BleOperationType? = null

Expand Down Expand Up @@ -288,6 +350,13 @@ class ImprovManager(
is Disconnect -> {
// Noop?
}
is DiscoverServices -> {
if (bluetoothGatt != null) {
bluetoothGatt!!.discoverServices()
} else {
Log.e(TAG, "Tried to discover services without device connected.")
}
}
is CharacteristicWrite -> {
if (bluetoothGatt != null) {
bluetoothGatt!!.writeCharacteristic(operation.char)
Expand All @@ -309,6 +378,13 @@ class ImprovManager(
Log.e(TAG, "Tried writing descriptor without device connected.")
}
}
is RequestLargeMtu -> {
if (bluetoothGatt != null) {
bluetoothGatt!!.requestMtu(517)
} else {
Log.e(TAG, "Tried requesting MTU without device connected.")
}
}
else -> {
error("Unhandled Operation!")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ interface ImprovManagerCallback {
fun onStateChange(state: DeviceState)

fun onErrorStateChange(errorState: ErrorState)

fun onRpcResult(result: List<String>)
}
Loading