diff --git a/app/src/main/java/com/example/sensortestingapp/ConnectionManager.kt b/app/src/main/java/com/example/sensortestingapp/ConnectionManager.kt index 9557c92..34d858b 100644 --- a/app/src/main/java/com/example/sensortestingapp/ConnectionManager.kt +++ b/app/src/main/java/com/example/sensortestingapp/ConnectionManager.kt @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package com.punchthrough.blestarterappandroid.ble +package com.example.sensortestingapp import android.annotation.SuppressLint import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothGatt import android.bluetooth.BluetoothGattCallback import android.bluetooth.BluetoothGattCharacteristic +import android.bluetooth.BluetoothGattDescriptor import android.bluetooth.BluetoothProfile import android.content.Context import android.util.Log @@ -28,6 +28,8 @@ import java.util.UUID import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentLinkedQueue +val CCC_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805F9B34FB") + private const val GATT_MIN_MTU_SIZE = 23 /** Maximum BLE MTU size as defined in gatt_api.h. */ @@ -59,6 +61,14 @@ data class ReadChar( val charId: UUID ) : BleOperationType() + +data class SetNotification( + override val device: BluetoothDevice, + val serviceId: UUID, + val charId: UUID, + val enable: Boolean +) : BleOperationType() + data class DiscoverServicesRequest( override val device: BluetoothDevice, ) : BleOperationType() @@ -70,6 +80,12 @@ open class BleListener { characteristic: BluetoothGattCharacteristic ) { } + + open fun onCharChange( + gatt: BluetoothGatt, + characteristic: BluetoothGattCharacteristic + ) { + } } @@ -173,6 +189,15 @@ object ConnectionManager { enqueueOperation(ReadChar(device, service, char)) } + fun enableNotification(device: BluetoothDevice, service: UUID, char: UUID) { + enqueueOperation(SetNotification(device, service, char, true)) + } + + fun disableNotification(device: BluetoothDevice, service: UUID, char: UUID) { + enqueueOperation(SetNotification(device, service, char, false)) + } + + // - Beginning of PRIVATE functions @Synchronized @@ -235,6 +260,7 @@ object ConnectionManager { return } + when (operation) { is Disconnect -> with(operation) { Log.w("ConnectionManager", "Disconnecting from ${device.address}") @@ -256,12 +282,67 @@ object ConnectionManager { if (characteristic?.isReadable() == true) { gatt.readCharacteristic(characteristic) } else { + Log.e("ConnectionManager", "Char $charId (${serviceId}) is not readable!") signalEndOfOperation() } } + is SetNotification -> with(operation) { + val characteristic = gatt.getService(serviceId)?.getCharacteristic(charId); + if (characteristic == null) { + Log.e("ConnectionManager", "Char $charId (${serviceId}) not found!") + signalEndOfOperation() + return + } + val descriptor = characteristic.getDescriptor(CCC_DESCRIPTOR_UUID) + if (!characteristic.isNotifiable() && !characteristic.isIndicatable()) { + Log.e( + "ConnectionManager", "Char ${characteristic.uuid} (service: $serviceId)" + + " doesn't support notifications/indications" + ) + signalEndOfOperation() + return + } + + val payload: ByteArray + + if (enable) { + if (!gatt.setCharacteristicNotification(characteristic, true)) { + Log.e( + "ConnectionManager", "setCharacteristicNotification to true " + + "failed for ${characteristic.uuid} (service: $serviceId)" + ) + signalEndOfOperation() + return + } + payload = + if (characteristic.isIndicatable()) + BluetoothGattDescriptor.ENABLE_INDICATION_VALUE + else BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE; + + } else { + if (!gatt.setCharacteristicNotification(characteristic, false)) { + Log.e( + "ConnectionManager", "setCharacteristicNotification to false " + + "failed for ${characteristic.uuid} (service: $serviceId)" + ) + signalEndOfOperation() + return + } + payload = BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE + } + + gatt.let { gatt -> + descriptor.value = payload + gatt.writeDescriptor(descriptor) + } + } + + + // necessary because of IDE type inference bug + // https://youtrack.jetbrains.com/issue/KTIJ-20749/Exhaustive-when-check-does-not-take-into-account-the-values-excluded-by-previous-if-conditions is Connect -> { Log.e("ConnectionManager", "Shouldn't get here") } @@ -365,11 +446,33 @@ object ConnectionManager { } } - if (pendingOperation is ReadChar) { + val op = pendingOperation + if (op is ReadChar && op.charId == characteristic.uuid && op.serviceId == characteristic.service.uuid) { signalEndOfOperation() } } + override fun onDescriptorWrite( + gatt: BluetoothGatt, + descriptor: BluetoothGattDescriptor, + status: Int + ) { + val op = pendingOperation + if (op is SetNotification && + descriptor.uuid == CCC_DESCRIPTOR_UUID + && op.charId == descriptor.characteristic.uuid && op.serviceId == descriptor.characteristic.service.uuid + ) { + signalEndOfOperation() + } + } + + override fun onCharacteristicChanged( + gatt: BluetoothGatt, + characteristic: BluetoothGattCharacteristic + ) { + notifyListeners { listener -> listener.onCharChange(gatt, characteristic) } + } + } diff --git a/app/src/main/java/com/example/sensortestingapp/MainActivity.kt b/app/src/main/java/com/example/sensortestingapp/MainActivity.kt index 70bf0d4..819c2e6 100644 --- a/app/src/main/java/com/example/sensortestingapp/MainActivity.kt +++ b/app/src/main/java/com/example/sensortestingapp/MainActivity.kt @@ -28,8 +28,6 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.SimpleItemAnimator import com.example.sensortestingapp.databinding.ActivityMainBinding -import com.punchthrough.blestarterappandroid.ble.BleListener -import com.punchthrough.blestarterappandroid.ble.ConnectionManager import java.nio.ByteBuffer import java.nio.ByteOrder import java.util.UUID @@ -138,21 +136,36 @@ class MainActivity : AppCompatActivity() { ConnectionManager.connect(scanResult.device, applicationContext) ConnectionManager.requestMtu(scanResult.device, Int.MAX_VALUE) ConnectionManager.discoverServices(scanResult.device) - ConnectionManager.readChar(scanResult.device, DEMO_SERVICE_UUID, DEMO_CHAR_UUID) + //ConnectionManager.readChar(scanResult.device, DEMO_SERVICE_UUID, DEMO_CHAR_UUID) + ConnectionManager.enableNotification( + scanResult.device, + DEMO_SERVICE_UUID, + DEMO_CHAR_UUID + ) } } private val bleListener = object : BleListener() { + + fun logDemoPayload(characteristic: BluetoothGattCharacteristic) { + if (characteristic.service.uuid == DEMO_SERVICE_UUID && characteristic.uuid == DEMO_CHAR_UUID) { + val payload = decodeDemoPayload(characteristic.value) + Log.i("BleListener", "Demo char received: $payload") + } + } + override fun onSuccessfulCharRead( gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic ) { + logDemoPayload(characteristic) + } - if (characteristic.service.uuid == DEMO_SERVICE_UUID && characteristic.uuid == DEMO_CHAR_UUID) { - val payload = decodeDemoPayload(characteristic.value) - Log.i("BleListener", "Demo char received: $payload") - - } + override fun onCharChange( + gatt: BluetoothGatt, + characteristic: BluetoothGattCharacteristic + ) { + logDemoPayload(characteristic) } }