diff --git a/app/src/main/java/com/wifi/improv/demo/ImprovViewModel.kt b/app/src/main/java/com/wifi/improv/demo/ImprovViewModel.kt index b8bc787..e840c1e 100644 --- a/app/src/main/java/com/wifi/improv/demo/ImprovViewModel.kt +++ b/app/src/main/java/com/wifi/improv/demo/ImprovViewModel.kt @@ -21,6 +21,7 @@ class ImprovViewModel : ViewModel(), ImprovManagerCallback { var connectedDevice: ImprovDevice? = null var deviceState: DeviceState? = null var errorState: ErrorState? = null + var rpcResult = listOf() private fun update() { viewModelScope.launch { @@ -31,7 +32,8 @@ class ImprovViewModel : ViewModel(), ImprovManagerCallback { connectedDevice?.address ?: "", connectedDevice != null, deviceState.toString(), - errorState.toString() + errorState.toString(), + rpcResult ) } } @@ -64,6 +66,11 @@ class ImprovViewModel : ViewModel(), ImprovManagerCallback { this.errorState = errorState update() } + + override fun onRpcResult(result: List) { + this.rpcResult = result + update() + } } @Immutable @@ -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 ) \ No newline at end of file diff --git a/app/src/main/java/com/wifi/improv/demo/MainActivity.kt b/app/src/main/java/com/wifi/improv/demo/MainActivity.kt index c606b7b..d01929f 100644 --- a/app/src/main/java/com/wifi/improv/demo/MainActivity.kt +++ b/app/src/main/java/com/wifi/improv/demo/MainActivity.kt @@ -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)) } @@ -132,7 +133,8 @@ fun ImprovStatus( name: String?, address: String?, deviceState: String?, - errorState: String? + errorState: String?, + rpcResult: List ) { Card(modifier = Modifier.fillMaxWidth()) { Column { @@ -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") } } } @@ -220,7 +223,8 @@ fun DefaultPreview() { "12:34:56:78", true, DeviceState.AUTHORIZED.toString(), - "errorState" + "errorState", + listOf() ) ImprovMain(screenState = improvScreenState, {}, {}, {}, { _, _ -> }) } \ No newline at end of file diff --git a/library/src/main/java/com/wifi/improv/BluetoothOperations.kt b/library/src/main/java/com/wifi/improv/BluetoothOperations.kt index b586ed1..0c47e10 100644 --- a/library/src/main/java/com/wifi/improv/BluetoothOperations.kt +++ b/library/src/main/java/com/wifi/improv/BluetoothOperations.kt @@ -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() diff --git a/library/src/main/java/com/wifi/improv/ImprovManager.kt b/library/src/main/java/com/wifi/improv/ImprovManager.kt index a514bd2..e4522e5 100644 --- a/library/src/main/java/com/wifi/improv/ImprovManager.kt +++ b/library/src/main/java/com/wifi/improv/ImprovManager.kt @@ -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 = @@ -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() @@ -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) @@ -98,6 +101,8 @@ 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) @@ -105,6 +110,14 @@ class ImprovManager( 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") + } } } @@ -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() } } @@ -257,6 +290,35 @@ class ImprovManager( enqueueOperation(CharacteristicWrite(rpc)) } + private fun extractResultStrings(data: ByteArray): List? { + // Ensure the data is at least 3 bytes long to read the first string length + if (data.size < 3) return null + + val strings = mutableListOf() + 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() private var pendingOperation: BleOperationType? = null @@ -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) @@ -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!") } diff --git a/library/src/main/java/com/wifi/improv/ImprovManagerCallback.kt b/library/src/main/java/com/wifi/improv/ImprovManagerCallback.kt index 3768b59..f1abe9d 100644 --- a/library/src/main/java/com/wifi/improv/ImprovManagerCallback.kt +++ b/library/src/main/java/com/wifi/improv/ImprovManagerCallback.kt @@ -11,4 +11,6 @@ interface ImprovManagerCallback { fun onStateChange(state: DeviceState) fun onErrorStateChange(errorState: ErrorState) + + fun onRpcResult(result: List) } \ No newline at end of file