feat: read demo characteristic
This commit is contained in:
@@ -20,9 +20,11 @@ import android.annotation.SuppressLint
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.bluetooth.BluetoothGatt
|
||||
import android.bluetooth.BluetoothGattCallback
|
||||
import android.bluetooth.BluetoothGattCharacteristic
|
||||
import android.bluetooth.BluetoothProfile
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
|
||||
@@ -51,6 +53,12 @@ data class MtuRequest(
|
||||
) : BleOperationType()
|
||||
|
||||
|
||||
data class ReadChar(
|
||||
override val device: BluetoothDevice,
|
||||
val serviceId: UUID,
|
||||
val charId: UUID
|
||||
) : BleOperationType()
|
||||
|
||||
data class DiscoverServicesRequest(
|
||||
override val device: BluetoothDevice,
|
||||
) : 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")
|
||||
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
|
||||
|
||||
@Synchronized
|
||||
@@ -192,6 +220,16 @@ object ConnectionManager {
|
||||
gatt.discoverServices()
|
||||
}
|
||||
|
||||
is ReadChar -> with(operation) {
|
||||
val characteristic = gatt.getService(serviceId)?.getCharacteristic(charId)
|
||||
if (characteristic?.isReadable() == true) {
|
||||
gatt.readCharacteristic(characteristic)
|
||||
} else {
|
||||
signalEndOfOperation()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
is Connect -> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,9 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||
import com.example.sensortestingapp.databinding.ActivityMainBinding
|
||||
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
|
||||
@@ -39,6 +42,11 @@ private const val GATT_MAX_MTU_SIZE = 517
|
||||
// Top level declaration
|
||||
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 {
|
||||
return ContextCompat.checkSelfPermission(this, permissionType) ==
|
||||
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")
|
||||
class MainActivity : AppCompatActivity() {
|
||||
@@ -101,9 +135,11 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
Reference in New Issue
Block a user