feat: read demo characteristic

This commit is contained in:
Fabian Christoffel
2023-06-20 15:14:34 +02:00
parent 5700bc8645
commit 96d1cb985d
2 changed files with 109 additions and 0 deletions

View File

@@ -20,9 +20,11 @@ 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.BluetoothProfile import android.bluetooth.BluetoothProfile
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue
@@ -51,6 +53,12 @@ data class MtuRequest(
) : BleOperationType() ) : BleOperationType()
data class ReadChar(
override val device: BluetoothDevice,
val serviceId: UUID,
val charId: UUID
) : BleOperationType()
data class DiscoverServicesRequest( data class DiscoverServicesRequest(
override val device: BluetoothDevice, override val device: BluetoothDevice,
) : BleOperationType() ) : BleOperationType()
@@ -76,6 +84,22 @@ private fun BluetoothGatt.printGattTable() {
} }
fun BluetoothGattCharacteristic.isReadable(): Boolean =
containsProperty(BluetoothGattCharacteristic.PROPERTY_READ)
fun BluetoothGattCharacteristic.isWritable(): Boolean =
containsProperty(BluetoothGattCharacteristic.PROPERTY_WRITE)
fun BluetoothGattCharacteristic.isWritableWithoutResponse(): Boolean =
containsProperty(BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE)
fun BluetoothGattCharacteristic.containsProperty(property: Int): Boolean {
return properties and property != 0
}
fun ByteArray.toHexString(): String =
joinToString(separator = "", prefix = "0x") { String.format("%02X", it) }
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
object ConnectionManager { object ConnectionManager {
@@ -114,6 +138,10 @@ object ConnectionManager {
} }
fun readChar(device: BluetoothDevice, service: UUID, char: UUID) {
enqueueOperation(ReadChar(device, service, char))
}
// - Beginning of PRIVATE functions // - Beginning of PRIVATE functions
@Synchronized @Synchronized
@@ -192,6 +220,16 @@ object ConnectionManager {
gatt.discoverServices() gatt.discoverServices()
} }
is ReadChar -> with(operation) {
val characteristic = gatt.getService(serviceId)?.getCharacteristic(charId)
if (characteristic?.isReadable() == true) {
gatt.readCharacteristic(characteristic)
} else {
signalEndOfOperation()
}
}
is Connect -> { is Connect -> {
Log.e("ConnectionManager", "Shouldn't get here") Log.e("ConnectionManager", "Shouldn't get here")
@@ -260,6 +298,41 @@ object ConnectionManager {
} }
} }
override fun onCharacteristicRead(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
with(characteristic) {
when (status) {
BluetoothGatt.GATT_SUCCESS -> {
Log.i(
"ConnectionManager",
"Read characteristic $uuid (service: ${service.uuid}): ${value.toHexString()}"
)
}
BluetoothGatt.GATT_READ_NOT_PERMITTED -> {
Log.e(
"BluetoothGattCallback",
"Read not permitted for $uuid (service: ${service.uuid})!"
)
}
else -> {
Log.e(
"BluetoothGattCallback",
"Characteristic read failed for $uuid (service: ${service.uuid}), error: $status"
)
}
}
}
if (pendingOperation is ReadChar) {
signalEndOfOperation()
}
}
} }

View File

@@ -27,6 +27,9 @@ 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.ConnectionManager import com.punchthrough.blestarterappandroid.ble.ConnectionManager
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.util.UUID
private const val ENABLE_BLUETOOTH_REQUEST_CODE = 1 private const val ENABLE_BLUETOOTH_REQUEST_CODE = 1
@@ -39,6 +42,11 @@ private const val GATT_MAX_MTU_SIZE = 517
// Top level declaration // Top level declaration
private const val GATT_REQUESTED_MTU_SIZE = GATT_MAX_MTU_SIZE private const val GATT_REQUESTED_MTU_SIZE = GATT_MAX_MTU_SIZE
private val DEMO_SERVICE_UUID = UUID.fromString("00000000-0001-11E1-9AB4-0002A5D5C51B")
private val DEMO_CHAR_UUID = UUID.fromString("00140000-0001-11E1-AC36-0002A5D5C51B")
fun Context.hasPermission(permissionType: String): Boolean { fun Context.hasPermission(permissionType: String): Boolean {
return ContextCompat.checkSelfPermission(this, permissionType) == return ContextCompat.checkSelfPermission(this, permissionType) ==
PackageManager.PERMISSION_GRANTED PackageManager.PERMISSION_GRANTED
@@ -53,6 +61,32 @@ fun Context.hasRequiredRuntimePermissions(): Boolean {
} }
} }
data class DemoPayload(val ts: Int, val pressure: Float, val temperature: Float)
fun bytesToUInt16(arr: ByteArray, start: Int): Int {
return ByteBuffer.wrap(arr, start, 2)
.order(ByteOrder.LITTLE_ENDIAN).short.toInt() and 0xFFFF
}
fun bytesToInt16(arr: ByteArray, start: Int): Short {
return ByteBuffer.wrap(arr, start, 2)
.order(ByteOrder.LITTLE_ENDIAN).short
}
fun bytesToInt32(arr: ByteArray, start: Int): Int {
return ByteBuffer.wrap(arr, start, 4)
.order(ByteOrder.LITTLE_ENDIAN).int
}
fun decodeDemoPayload(bytes: ByteArray): DemoPayload {
val ts = bytesToUInt16(bytes, 0)
val pressure = bytesToInt32(bytes, 2) / 100.0f
val temp = bytesToInt16(bytes, 6) / 10.0f;
return DemoPayload(ts, pressure, temp)
}
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
@@ -101,9 +135,11 @@ 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)
} }
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)