Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement characteristic instance id support #5

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 44 additions & 9 deletions android/src/main/kotlin/com/rohit/ble_peripheral/BleExtension.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,17 @@ import java.lang.Exception
import java.util.Collections
import java.util.UUID

private val bluetoothGattCharacteristics: MutableMap<String, BluetoothGattCharacteristic> =
HashMap()
data class BleCharCache(
val uuid: String,
val instanceId: Int?,
val char: BluetoothGattCharacteristic,
)

private val bluetoothGattCharacteristics: MutableList<BleCharCache> = mutableListOf()
private val descriptorValueReadMap: MutableMap<String, ByteArray> =
HashMap()
private val instanceIdMap: MutableMap<Int, Int> =
HashMap()
const val descriptorCCUUID = "00002902-0000-1000-8000-00805f9b34fb"


Expand Down Expand Up @@ -51,6 +58,12 @@ fun BleCharacteristic.toGattCharacteristic(): BluetoothGattCharacteristic {
properties.toPropertiesList(),
permissions.toPermissionsList()
)

// Store instance Id in map to identify
instanceId?.let {
instanceIdMap[char.instanceId] = it.toInt()
}

value?.let {
char.value = it
}
Expand All @@ -62,9 +75,24 @@ fun BleCharacteristic.toGattCharacteristic(): BluetoothGattCharacteristic {

addCCDescriptorIfRequired(this, char)

if (bluetoothGattCharacteristics[uuid] == null) {
bluetoothGattCharacteristics[uuid] = char

if (instanceId == null) {
// Then all good, make sure only one char exists
if (bluetoothGattCharacteristics.any { it.uuid == char.uuid.toString() }) {
throw FlutterError(code = "Failed", message = "Char already exists")
}
bluetoothGattCharacteristics.add(BleCharCache(uuid, null, char))
} else {
// Make sure this instanceId does not exists
if (bluetoothGattCharacteristics.any { it.uuid == char.uuid.toString() && it.instanceId == instanceId.toInt() }) {
throw FlutterError(
code = "Failed",
message = "Char already exists with this instance id"
)
}
bluetoothGattCharacteristics.add(BleCharCache(uuid, instanceId.toInt(), char))
}

return char
}

Expand Down Expand Up @@ -123,14 +151,17 @@ fun BleDescriptor.toGattDescriptor(): BluetoothGattDescriptor {
return descriptor
}

fun String.findCharacteristic(): BluetoothGattCharacteristic? {
return bluetoothGattCharacteristics[this]
fun String.findCharacteristic(instanceId: Long?): BluetoothGattCharacteristic? {
if (instanceId != null) {
return bluetoothGattCharacteristics.find { it.uuid == this && instanceId.toInt() == it.instanceId }?.char
}
return bluetoothGattCharacteristics.find { it.uuid == this }?.char
}

fun String.findService(): BluetoothGattService? {
for (char in bluetoothGattCharacteristics.values) {
if (char.service?.uuid.toString() == this) {
return char.service
for (char in bluetoothGattCharacteristics) {
if (char.char.service?.uuid.toString() == this) {
return char.char.service
}
}
return null
Expand Down Expand Up @@ -175,4 +206,8 @@ fun Int.toBondState(): BondState {
BluetoothDevice.BOND_NONE -> BondState.NONE
else -> BondState.NONE
}
}

fun Int.toGivenInstanceId(): Long? {
return instanceIdMap[this]?.toLong()
}
24 changes: 14 additions & 10 deletions android/src/main/kotlin/com/rohit/ble_peripheral/BlePeripheral.g.kt
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,8 @@ data class BleCharacteristic (
val properties: List<CharacteristicProperties>,
val permissions: List<AttributePermissions>,
val descriptors: List<BleDescriptor>? = null,
val value: ByteArray? = null
val value: ByteArray? = null,
val instanceId: Long? = null
)
{
companion object {
Expand All @@ -159,7 +160,8 @@ data class BleCharacteristic (
val permissions = pigeonVar_list[2] as List<AttributePermissions>
val descriptors = pigeonVar_list[3] as List<BleDescriptor>?
val value = pigeonVar_list[4] as ByteArray?
return BleCharacteristic(uuid, properties, permissions, descriptors, value)
val instanceId = pigeonVar_list[5] as Long?
return BleCharacteristic(uuid, properties, permissions, descriptors, value, instanceId)
}
}
fun toList(): List<Any?> {
Expand All @@ -169,6 +171,7 @@ data class BleCharacteristic (
permissions,
descriptors,
value,
instanceId,
)
}
}
Expand Down Expand Up @@ -385,7 +388,7 @@ interface BlePeripheralChannel {
fun getServices(): List<String>
fun getSubscribedClients(): List<SubscribedClient>
fun startAdvertising(services: List<String>, localName: String?, timeout: Long?, manufacturerData: ManufacturerData?, addManufacturerDataInScanResponse: Boolean)
fun updateCharacteristic(characteristicId: String, value: ByteArray, deviceId: String?)
fun updateCharacteristic(characteristicId: String, value: ByteArray, deviceId: String?, instanceId: Long?)

companion object {
/** The codec used by BlePeripheralChannel. */
Expand Down Expand Up @@ -585,8 +588,9 @@ interface BlePeripheralChannel {
val characteristicIdArg = args[0] as String
val valueArg = args[1] as ByteArray
val deviceIdArg = args[2] as String?
val instanceIdArg = args[3] as Long?
val wrapped: List<Any?> = try {
api.updateCharacteristic(characteristicIdArg, valueArg, deviceIdArg)
api.updateCharacteristic(characteristicIdArg, valueArg, deviceIdArg, instanceIdArg)
listOf(null)
} catch (exception: Throwable) {
wrapError(exception)
Expand All @@ -612,12 +616,12 @@ class BleCallback(private val binaryMessenger: BinaryMessenger, private val mess
BlePeripheralPigeonCodec()
}
}
fun onReadRequest(deviceIdArg: String, characteristicIdArg: String, offsetArg: Long, valueArg: ByteArray?, callback: (Result<ReadRequestResult?>) -> Unit)
fun onReadRequest(deviceIdArg: String, characteristicIdArg: String, offsetArg: Long, valueArg: ByteArray?, instanceIdArg: Long?, callback: (Result<ReadRequestResult?>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.ble_peripheral.BleCallback.onReadRequest$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(deviceIdArg, characteristicIdArg, offsetArg, valueArg)) {
channel.send(listOf(deviceIdArg, characteristicIdArg, offsetArg, valueArg, instanceIdArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
Expand All @@ -630,12 +634,12 @@ class BleCallback(private val binaryMessenger: BinaryMessenger, private val mess
}
}
}
fun onWriteRequest(deviceIdArg: String, characteristicIdArg: String, offsetArg: Long, valueArg: ByteArray?, callback: (Result<WriteRequestResult?>) -> Unit)
fun onWriteRequest(deviceIdArg: String, characteristicIdArg: String, offsetArg: Long, valueArg: ByteArray?, instanceIdArg: Long?, callback: (Result<WriteRequestResult?>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.ble_peripheral.BleCallback.onWriteRequest$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(deviceIdArg, characteristicIdArg, offsetArg, valueArg)) {
channel.send(listOf(deviceIdArg, characteristicIdArg, offsetArg, valueArg, instanceIdArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
Expand All @@ -648,12 +652,12 @@ class BleCallback(private val binaryMessenger: BinaryMessenger, private val mess
}
}
}
fun onCharacteristicSubscriptionChange(deviceIdArg: String, characteristicIdArg: String, isSubscribedArg: Boolean, nameArg: String?, callback: (Result<Unit>) -> Unit)
fun onCharacteristicSubscriptionChange(deviceIdArg: String, characteristicIdArg: String, isSubscribedArg: Boolean, nameArg: String?, instanceIdArg: Long?, callback: (Result<Unit>) -> Unit)
{
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
val channelName = "dev.flutter.pigeon.ble_peripheral.BleCallback.onCharacteristicSubscriptionChange$separatedMessageChannelSuffix"
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
channel.send(listOf(deviceIdArg, characteristicIdArg, isSubscribedArg, nameArg)) {
channel.send(listOf(deviceIdArg, characteristicIdArg, isSubscribedArg, nameArg, instanceIdArg)) {
if (it is List<*>) {
if (it.size > 1) {
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ private const val TAG = "BlePeripheralPlugin"

@SuppressLint("MissingPermission")
class BlePeripheralPlugin : FlutterPlugin, BlePeripheralChannel, ActivityAware {
// PluginRegistry.ActivityResultListener {
private val requestCodeBluetoothPermission = 0xa1c
private var bleCallback: BleCallback? = null
private val requestCodeBluetoothEnablePermission = 0xb1e
Expand All @@ -47,7 +46,8 @@ class BlePeripheralPlugin : FlutterPlugin, BlePeripheralChannel, ActivityAware {
private var bluetoothLeAdvertiser: BluetoothLeAdvertiser? = null
private var gattServer: BluetoothGattServer? = null
private val bluetoothDevicesMap: MutableMap<String, BluetoothDevice> = HashMap()
private val subscribedCharDevicesMap: MutableMap<String, MutableList<String>> = HashMap()
private val subscribedCharDevicesMap: MutableMap<String, MutableList<BluetoothGattCharacteristic>> =
HashMap()
private val emptyBytes = byteArrayOf()
private val listOfDevicesWaitingForBond = mutableListOf<String>()
private var isAdvertising: Boolean? = null
Expand Down Expand Up @@ -108,7 +108,11 @@ class BlePeripheralPlugin : FlutterPlugin, BlePeripheralChannel, ActivityAware {
}

override fun getSubscribedClients(): List<SubscribedClient> {
return subscribedCharDevicesMap.map { data -> SubscribedClient(data.key, data.value) }
return subscribedCharDevicesMap.map { data ->
SubscribedClient(
data.key,
data.value.map { it.uuid.toString() })
}
}

override fun startAdvertising(
Expand Down Expand Up @@ -183,9 +187,10 @@ class BlePeripheralPlugin : FlutterPlugin, BlePeripheralChannel, ActivityAware {
characteristicId: String,
value: ByteArray,
deviceId: String?,
instanceId: Long?,
) {
val char =
characteristicId.findCharacteristic() ?: throw Exception("Characteristic not found")
characteristicId.findCharacteristic(instanceId) ?: throw Exception("Characteristic not found")
char.value = value
if (deviceId != null) {
val device = bluetoothDevicesMap[deviceId] ?: throw Exception("Device not found")
Expand Down Expand Up @@ -238,17 +243,17 @@ class BlePeripheralPlugin : FlutterPlugin, BlePeripheralChannel, ActivityAware {

private fun cleanConnection(device: BluetoothDevice) {
val deviceAddress = device.address

// Notify char unsubscribe event on disconnect
val subscribedCharUUID: MutableList<String> =
val subscribedCharUUID: MutableList<BluetoothGattCharacteristic> =
subscribedCharDevicesMap[deviceAddress] ?: mutableListOf()
subscribedCharUUID.forEach { charUUID ->
subscribedCharUUID.forEach { char ->
handler?.post {
bleCallback?.onCharacteristicSubscriptionChange(
deviceAddress,
charUUID,
char.uuid.toString(),
false,
device.name
device.name,
char.instanceId.toGivenInstanceId()
) {}
}
}
Expand Down Expand Up @@ -342,6 +347,7 @@ class BlePeripheralPlugin : FlutterPlugin, BlePeripheralChannel, ActivityAware {
characteristicIdArg = characteristic.uuid.toString(),
offsetArg = offset.toLong(),
valueArg = characteristic.value,
instanceIdArg = characteristic.instanceId.toGivenInstanceId()
) { it: Result<ReadRequestResult?> ->
val readRequestResult: ReadRequestResult? = it.getOrNull()
if (readRequestResult == null) {
Expand Down Expand Up @@ -389,6 +395,7 @@ class BlePeripheralPlugin : FlutterPlugin, BlePeripheralChannel, ActivityAware {
characteristicIdArg = characteristic.uuid.toString(),
offsetArg = offset.toLong(),
valueArg = value,
instanceIdArg = characteristic.instanceId.toGivenInstanceId()
) {
val writeResult: WriteRequestResult? = it.getOrNull()
gattServer?.sendResponse(
Expand Down Expand Up @@ -472,28 +479,31 @@ class BlePeripheralPlugin : FlutterPlugin, BlePeripheralChannel, ActivityAware {
|| BluetoothGattDescriptor.ENABLE_INDICATION_VALUE.contentEquals(
value
)
val characteristicId = descriptor.characteristic.uuid.toString()
val characteristic = descriptor.characteristic
val characteristicId = characteristic.uuid.toString()
device?.address?.let {
handler?.post {
bleCallback?.onCharacteristicSubscriptionChange(
it,
characteristicId,
isSubscribed,
device.name
device.name,
instanceIdArg = descriptor.characteristic.instanceId.toGivenInstanceId()
) {}
}

// Update subscribed char list
val charList = subscribedCharDevicesMap[it] ?: mutableListOf()

val item: BluetoothGattCharacteristic? =
charList.find { c -> c.uuid.toString() == characteristicId && c.instanceId == characteristic.instanceId }

if (isSubscribed) {
if (!charList.contains(characteristicId)) {
charList.add(characteristicId)
}
} else if (charList.contains(characteristicId)) {
if (charList.contains(characteristicId)) {
charList.remove(characteristicId)
if (item == null) {
charList.add(characteristic)
}
} else if (item != null) {
charList.remove(item)
}

if (charList.isEmpty()) {
Expand Down Expand Up @@ -645,13 +655,4 @@ class BlePeripheralPlugin : FlutterPlugin, BlePeripheralChannel, ActivityAware {
}
return false
}

// override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
// if (requestCode == requestCodeBluetoothPermission) {
// Log.d(TAG, "onActivityResultForBlePermission: ${resultCode == Activity.RESULT_OK}")
// } else if (requestCode == requestCodeBluetoothEnablePermission) {
// Log.d(TAG, "onActivityResultForBleEnable: ${resultCode == Activity.RESULT_OK}")
// }
// return false
// }
}
Loading