0

I want to be able to receive notifications on exactly 2 characteristics. Said characteristics are RX and TX from the perspective of the BLE device I am communicating with. I have succeeded in doing it for the RX, but apparently I need to do the same for the TX.

   override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
        with(gatt) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                val services = gatt.services
                Timber.w("Discovered ${services.size} services for ${device.address}.")
                printGattTable()
                requestMtu(device, GATT_MAX_MTU_SIZE)
                val characteristic: BluetoothGattCharacteristic = this.getService(XpressStreamingServiceUUID).getCharacteristic(peripheralRX)
                this.setCharacteristicNotification(characteristic, true)
                setBleCharacteristic(characteristic)
                listeners.forEach { it.get()?.onConnectionSetupComplete?.invoke(this) }
            } else {
                Timber.e("Service discovery failed due to status $status")
                teardownConnection(gatt.device)
                disconnect()
            }
        }
        if (pendingOperation is Connect) {
            signalEndOfOperation()
        }
    }

how do I go about changing that? Would a function like this suffice?:

 private fun setNotification(
    characteristic: BluetoothGattCharacteristic,
    enabled: Boolean
) {
    bluetoothGattRef.let { gatt ->
        gatt.setCharacteristicNotification(characteristic, enabled)

        if (peripheralTX == characteristic.uuid) {
            val descriptor = characteristic.getDescriptor(UUID.fromString(CCC_DESCRIPTOR_UUID))
            descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
            gatt.writeDescriptor(descriptor)
        } else if (peripheralRX == characteristic.uuid) {
            val descriptor = characteristic.getDescriptor(UUID.fromString(CCC_DESCRIPTOR_UUID))
            descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
            gatt.writeDescriptor(descriptor)
        }
        else{
            Timber.i("Neither characteristic RX nor TX detected")
        }
    }
}

I basically want to be notified by either one of them when send or receiving data

UPDATE: would this be a proper queuing mechanism?

 @Synchronized
private fun enqueueOperation(operation: BleOperationType) {
    operationQueue.add(operation)
    if (pendingOperation == null) {
        doNextOperation()
    }
}


@Synchronized
private fun signalEndOfOperation() {
    Timber.d("End of $pendingOperation")
    pendingOperation = null
    if (operationQueue.isNotEmpty()) {
        doNextOperation()
    }
}
    @Synchronized
private fun doNextOperation() {
    if (pendingOperation != null) {
        Timber.e("doNextOperation() called when an operation is pending! Aborting.")
        return
    }

    val operation = operationQueue.poll() ?: run {
        Timber.v("Operation queue empty, returning")
        return
    }
    pendingOperation = operation

    // Handle Connect separately from other operations that require device to be connected
    if (operation is Connect) {
        with(operation) {
            Timber.w("Connecting to ${device.name} | ${device.address}")
            device.connectGatt(context, false, gattCallback)
            isConnected.value = true
        }
        return
    }

    // Check BluetoothGatt availability for other operations
    val gatt = deviceGattMap[operation.device]
        ?: this@BleConnectionManager.run {
            Timber.e("Not connected to ${operation.device.address}! Aborting $operation operation.")
            signalEndOfOperation()
            return
        }

    // TODO: Make sure each operation ultimately leads to signalEndOfOperation()
    // TODO: Refactor this into an BleOperationType abstract or extension function
    when (operation) {
        is Disconnect -> with(operation) {
            Timber.w("Disconnecting from ${device.address}")
            gatt.close()
            deviceGattMap.remove(device)
            listeners.forEach { it.get()?.onDisconnect?.invoke(device) }
            signalEndOfOperation()
            setConnectionStatus(STATE_DISCONNECTED)
            isConnected.value = false
        }
        is CharacteristicWrite -> with(operation) {
            gatt.findCharacteristic(characteristicUUID)?.let { characteristic ->
                characteristic.writeType = writeType
                characteristic.value = payLoad
                gatt.writeCharacteristic(characteristic)
            } ?: this@BleConnectionManager.run {
                Timber.e("Cannot find $characteristicUUID to write to")
                signalEndOfOperation()
            }
        }
        is CharacteristicRead -> with(operation) {
            gatt.findCharacteristic(characteristicUUID)?.let { characteristic ->
                gatt.readCharacteristic(characteristic)
            } ?: this@BleConnectionManager.run {
                Timber.e("Cannot find $characteristicUUID to read from")
                signalEndOfOperation()
            }
        }
        is DescriptorWrite -> with(operation) {
            gatt.findDescriptor(descriptorUUID)?.let { descriptor ->
                descriptor.value = payLoad
                gatt.writeDescriptor(descriptor)
            } ?: this@BleConnectionManager.run {
                Timber.e("Cannot find $descriptorUUID to write to")
                signalEndOfOperation()
            }
        }
        is DescriptorRead -> with(operation) {
            gatt.findDescriptor(descriptorUUID)?.let { descriptor ->
                gatt.readDescriptor(descriptor)
            } ?: this@BleConnectionManager.run {
                Timber.e("Cannot find $descriptorUUID to read from")
                signalEndOfOperation()
            }
        }
        is EnableNotifications -> with(operation) {
            gatt.findCharacteristic(characteristicUUID)?.let { characteristic ->
                val cccdUuid = UUID.fromString(CCC_DESCRIPTOR_UUID)
                val payload = when {
                    characteristic.isIndicatable() ->
                        BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
                    characteristic.isNotifiable() ->
                        BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
                    else ->
                        error("${characteristic.uuid} doesn't support notifications/indications")
                }

                characteristic.getDescriptor(cccdUuid)?.let { cccDescriptor ->
                    if (!gatt.setCharacteristicNotification(characteristic, true)) {
                        Timber.e("setCharacteristicNotification failed for ${characteristic.uuid}")
                        signalEndOfOperation()
                        return
                    }

                    cccDescriptor.value = payload
                    gatt.writeDescriptor(cccDescriptor)
                } ?: this@BleConnectionManager.run {
                    Timber.e("${characteristic.uuid} doesn't contain the CCC descriptor!")
                    signalEndOfOperation()
                }
            } ?: this@BleConnectionManager.run {
                Timber.e("Cannot find $characteristicUUID! Failed to enable notifications.")
                signalEndOfOperation()
            }
        }
        is DisableNotifications -> with(operation) {
            gatt.findCharacteristic(characteristicUUID)?.let { characteristic ->
                val cccdUuid = UUID.fromString(CCC_DESCRIPTOR_UUID)
                characteristic.getDescriptor(cccdUuid)?.let { cccDescriptor ->
                    if (!gatt.setCharacteristicNotification(characteristic, false)) {
                        Timber.e("setCharacteristicNotification failed for ${characteristic.uuid}")
                        signalEndOfOperation()
                        return
                    }

                    cccDescriptor.value = BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
                    gatt.writeDescriptor(cccDescriptor)
                } ?: this@BleConnectionManager.run {
                    Timber.e("${characteristic.uuid} doesn't contain the CCC descriptor!")
                    signalEndOfOperation()
                }
            } ?: this@BleConnectionManager.run {
                Timber.e("Cannot find $characteristicUUID! Failed to disable notifications.")
                signalEndOfOperation()
            }
        }
        is MtuRequest -> with(operation) {
            gatt.requestMtu(mtu)
        }
    }
}
  • 2
    Yes something like that should work. Just note that you must wait for the previous GATT operation to finish (by waiting for the corresponding callback) before you can execute the next one. GATT operations include MTU requests and Write Descriptor. But with your names TX/RX I would expect that only one requires notification... – Emil Jan 09 '22 at 20:54
  • I read everywhere about waiting for one operation to end to start another, when you say "corresponding callback" what do you mean exactly? Also where do I check for notifications? In the onCharacteristicChanged override function? – Mohamed El Kayal Jan 10 '22 at 13:08
  • 1
    Your goal seems possible, but not fully clear for me. So, you have BLE Central connected to BLE Peripheral. As far as I know, there are no "RX and TX" in BLE terminology, every characteristic can support any combination of these communication options: read, write, notify. Read - Central asks Peripheral "give me the value", write - Central says Peripheral "put this new value there", notify - Central subscribes, and after that Peripheral sends notifications "hey, this value has been changed". My question is: after Central writes TX, when exactly do you want your Central to be notified? – alexander.cpp Jan 10 '22 at 21:26
  • 1
    Corresponding callback means, for example for writeDescriptor: onDescriptorWrite. – Emil Jan 10 '22 at 22:22
  • @alexander.cpp The RX is the characteristic used to send data to the peripheral device where as the TX is to receive data from the peripheral device as described here: https://docs.silabs.com/gecko-os/1/bgx/latest/ble-services – Mohamed El Kayal Jan 11 '22 at 12:36
  • 1
    @MohamedElKayal I understand now, documentation from Silicon Labs is a strong argument :) Then basically yes, your function should work to subscribe, but a few remarks: 1) there is some common code for subscribing to RX and TX, common code could be moved outside of ifs, 2) the function will work only when enable == true, because to unsubscribe you should use `BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE`. I also very argee with Emil (need to wait for previous operation finish - writeDescriptor: onDescriptorWrite). – alexander.cpp Jan 11 '22 at 16:56
  • This example might be also helpful: https://github.com/alexanderlavrushko/BLEProof-collection/blob/5cbb089dbfed42cfb8aad0db236a376ff1cce620/Android/BLEProofCentral/app/src/main/java/com/rpt11/bleproofcentral/MainActivity.kt#L222 (it uses indications instead of notificaitons) – alexander.cpp Jan 11 '22 at 17:05
  • @alexander.cpp I made an update regarding the queuing of operations, wanted to hear your thoughts on it – Mohamed El Kayal Jan 11 '22 at 17:25
  • @MohamedElKayal queuing looks good to me, especially because it's based on "The Ultimate Guide to Android BLE" by PunchThrough. However I have not enough experience with Android so far, and I've not implemented this kind of queuing myself yet, I just know every guide says that waiting for previous operation is necessary. – alexander.cpp Jan 11 '22 at 20:55
  • @alexander.cpp your help is appreciated. The last thing I want to know is how does one acquire the data that the BLE Device is sending? If the notifications are enabled on the CharacteristicTX as per the documentation. What's the next move to get the data so I can view it? Something seems missing but I don't know what exactly. – Mohamed El Kayal Jan 12 '22 at 11:37
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/240993/discussion-between-alexander-cpp-and-mohamed-el-kayal). – alexander.cpp Jan 12 '22 at 12:02

0 Answers0