feat: subscribe to demo char changes

This commit is contained in:
Fabian Christoffel
2023-06-20 17:45:27 +02:00
parent 909e85d676
commit 2be1a5d6e9
2 changed files with 127 additions and 11 deletions

View File

@@ -13,14 +13,14 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.example.sensortestingapp
package com.punchthrough.blestarterappandroid.ble
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback import android.bluetooth.BluetoothGattCallback
import android.bluetooth.BluetoothGattCharacteristic import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothProfile import android.bluetooth.BluetoothProfile
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
@@ -28,6 +28,8 @@ import java.util.UUID
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue
val CCC_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805F9B34FB")
private const val GATT_MIN_MTU_SIZE = 23 private const val GATT_MIN_MTU_SIZE = 23
/** Maximum BLE MTU size as defined in gatt_api.h. */ /** Maximum BLE MTU size as defined in gatt_api.h. */
@@ -59,6 +61,14 @@ data class ReadChar(
val charId: UUID val charId: UUID
) : BleOperationType() ) : BleOperationType()
data class SetNotification(
override val device: BluetoothDevice,
val serviceId: UUID,
val charId: UUID,
val enable: Boolean
) : BleOperationType()
data class DiscoverServicesRequest( data class DiscoverServicesRequest(
override val device: BluetoothDevice, override val device: BluetoothDevice,
) : BleOperationType() ) : BleOperationType()
@@ -70,6 +80,12 @@ open class BleListener {
characteristic: BluetoothGattCharacteristic characteristic: BluetoothGattCharacteristic
) { ) {
} }
open fun onCharChange(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic
) {
}
} }
@@ -173,6 +189,15 @@ object ConnectionManager {
enqueueOperation(ReadChar(device, service, char)) 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 // - Beginning of PRIVATE functions
@Synchronized @Synchronized
@@ -235,6 +260,7 @@ object ConnectionManager {
return return
} }
when (operation) { when (operation) {
is Disconnect -> with(operation) { is Disconnect -> with(operation) {
Log.w("ConnectionManager", "Disconnecting from ${device.address}") Log.w("ConnectionManager", "Disconnecting from ${device.address}")
@@ -256,12 +282,67 @@ object ConnectionManager {
if (characteristic?.isReadable() == true) { if (characteristic?.isReadable() == true) {
gatt.readCharacteristic(characteristic) gatt.readCharacteristic(characteristic)
} else { } else {
Log.e("ConnectionManager", "Char $charId (${serviceId}) is not readable!")
signalEndOfOperation() 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 -> { is Connect -> {
Log.e("ConnectionManager", "Shouldn't get here") 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() 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) }
}
} }

View File

@@ -28,8 +28,6 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator import androidx.recyclerview.widget.SimpleItemAnimator
import com.example.sensortestingapp.databinding.ActivityMainBinding 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.ByteBuffer
import java.nio.ByteOrder import java.nio.ByteOrder
import java.util.UUID import java.util.UUID
@@ -138,21 +136,36 @@ class MainActivity : AppCompatActivity() {
ConnectionManager.connect(scanResult.device, applicationContext) ConnectionManager.connect(scanResult.device, applicationContext)
ConnectionManager.requestMtu(scanResult.device, Int.MAX_VALUE) ConnectionManager.requestMtu(scanResult.device, Int.MAX_VALUE)
ConnectionManager.discoverServices(scanResult.device) 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() { 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( override fun onSuccessfulCharRead(
gatt: BluetoothGatt, gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic 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)
} }
} }