The function onCharacteristicChanged is called just one time every time i call a writecharacteristic. What i expect to happen is onCharacteristicChanged to be called everytime something actually change. For exemple the device I'm using have a characteristic that gets the time of the system, so this characteristic change every second but onCharacteristicChanged doesn't notify me that this change is happening if I don't call the writecharacteristic method
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
with(characteristic) {
addGlog("\uD83D\uDCE2 BluetoothGattCallback: Characteristic $uuid changed | value: ${value.toHexString()}")
}
}
#SuppressLint("MissingPermission")
fun writeDescriptor(descriptor: BluetoothGattDescriptor, payload: ByteArray) {
actual_gatt?.let { gatt ->
descriptor.value = payload
gatt.writeDescriptor(descriptor)
} ?: addGlog("Not connected to a BLE device!")
}
#SuppressLint("MissingPermission")
fun enableNotifications(characteristic: BluetoothGattCharacteristic) { //nuova versione il meno invasiva possibile
var cccdUuid = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb") //valore temporaneo
addGlog("BluetoothGattDescriptor for ${characteristic.uuid.toString()}")
for (descriptor in characteristic.descriptors) {
addGlog("> " + descriptor.uuid.toString())
cccdUuid = descriptor.uuid
}
val payload = when {
characteristic.isIndicatable() -> BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
characteristic.isNotifiable() -> BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
else -> {
addGlog("ConnectionManager: ${characteristic.uuid} doesn't support notifications/indications")
return
}
}
characteristic.getDescriptor(cccdUuid)?.let { cccDescriptor ->
if (actual_gatt?.setCharacteristicNotification(characteristic, true) == false) {
addGlog("ConnectionManager: setCharacteristicNotification failed for ${characteristic.uuid}")
return
}
writeDescriptor(cccDescriptor, payload)
addGlog("\uD83D\uDD14 enableNotification with cccDescriptor: ${cccDescriptor.toString()} and cccdUuid ${cccdUuid.toString()} completed")
} ?: addGlog("ConnectionManager: ${characteristic.uuid} doesn't contain the CCC descriptor!")
}
Related
BLE device gets the data when it is disconnected from the gatt server.
I am trying to send data to the BLE device through the GATT server. When I try to send data which is command (string) to the BLE device, gattscancallback is called and it says write successful but nothing happens to the BLE device. But when I exit the application, the BLE device gets the data. I found that BLE device gets the data when it is disconnected to the gatt server. How do I solve this problem?
this is my GattCallback
private val gattCallback: BluetoothGattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
super.onConnectionStateChange(gatt, status, newState)
if( status == BluetoothGatt.GATT_FAILURE ) {
disconnect()
return
} else if( status != BluetoothGatt.GATT_SUCCESS ) {
disconnect()
return
}
if( newState == BluetoothProfile.STATE_CONNECTED ) {
// update the connection status message
Log.d(TAG, "Connected to the GATT server")
gatt.discoverServices()
} else if ( newState == BluetoothProfile.STATE_DISCONNECTED ) {
disconnect()
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
super.onServicesDiscovered(gatt, status)
// check if the discovery failed
if (status != BluetoothGatt.GATT_SUCCESS) {
Log.e(TAG, "Device service discovery failed, status: $status")
return
}
// log for successful discovery
Log.d(TAG, "Services discovery is successful")
isStatuschanged = "connected"
// find command characteristics from the GATT server
val respCharacteristic = gatt?.let { BluetoothUtils.findResponseCharacteristic(it) }
// disconnect if the characteristic is not found
if( respCharacteristic == null ) {
Log.e(TAG, "Unable to find cmd characteristic")
disconnect()
return
}
gatt.setCharacteristicNotification(respCharacteristic, true)
// UUID for notification
val descriptor: BluetoothGattDescriptor = respCharacteristic.getDescriptor(
UUID.fromString(CLIENT_CHARACTERISTIC_CONFIG)
)
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
gatt.writeDescriptor(descriptor)
}
override fun onCharacteristicChanged(
gatt: BluetoothGatt?,
characteristic: BluetoothGattCharacteristic
) {
super.onCharacteristicChanged(gatt, characteristic)
//Log.d(TAG, "characteristic changed: " + characteristic.uuid.toString())
readCharacteristic(characteristic)
}
override fun onCharacteristicWrite(
gatt: BluetoothGatt?,
characteristic: BluetoothGattCharacteristic?,
status: Int
) {
super.onCharacteristicWrite(gatt, characteristic, status)
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d(TAG, "Characteristic written successfully")
} else {
Log.e(TAG, "Characteristic write unsuccessful, status: $status")
disconnect()
}
}
override fun onCharacteristicRead(
gatt: BluetoothGatt?,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
super.onCharacteristicRead(gatt, characteristic, status)
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.d(TAG, "Characteristic read successfully")
readCharacteristic(characteristic)
} else {
Log.e(TAG, "Characteristic read unsuccessful, status: $status")
// Trying to read from the Time Characteristic? It doesnt have the property or permissions
// set to allow this. Normally this would be an error and you would want to:
// disconnectGattServer()
}
}
/**
* Log the value of the characteristic
* #param characteristic
*/
}
this is my write function
fun write(message: String) {
val cmdCharacteristic = BluetoothUtils.findCommandCharacteristic(bluetoothGatt!!)
if (cmdCharacteristic == null) {
Log.e(TAG, "Unable to find cmd characteristic")
disconnect()
return
}
cmdCharacteristic?.value = message.toByteArray()
//bluetoothGatt!!.writeCharacteristic(cmdCharacteristic)
val success: Boolean = bluetoothGatt!!.writeCharacteristic(cmdCharacteristic)
if (!success) {
Log.d("jay", "failed to write command")
}
Log.d("jay", "write succesful")
//disconnect()
}
this is my disconnect function
fun disconnect() {
Log.d("jay", "disconnect: bluetoothGatt is null? ${bluetoothGatt == null}")
bluetoothDeviceAddress = null
bluetoothGatt?.disconnect()
isStatuschanged = "disconnected"
bluetoothGatt = null
}
Can you show the disconnect() method? Because if you wrote that your central device sends data to the ble device when the application closes, it means that gattCallback's onConnectionState is triggered with a call to newState == BluetoothProfile.STATE_DISCONNECTED and a call to the disconnet() method.
Perhaps the problem is that you explicitly need to specify the record type for the characteristic for example: BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT or BluetoothGattCharacteristic.WRITE_TYPE_WITHOUT_RESPONSE.
Also, there may be an error in the calling code, for example, if you use coroutines and manage them incorrectly
I recently had a similar problem when I solved it by specifying the feature write type and adding the setCharacteristicNotification() method
I had a similar problem recently, but I solved it by specifying the feature record type, and adding the setCharacteristicNotification method
I have a characteristic that when written to the BLE device I am attempting to communicate with is supposed to respond with certain data.
When I enabled notification on the characteristic that is used when sending data, the onCharacteristicChanged function was called. But when I enabled notifications on the characteristic that I am supposed to use when receiving data, it never gets called.
My app is supposed to communicate with the BGX ble kit. The documentation here: https://docs.silabs.com/gecko-os/1/bgx/latest/ble-services
According to the link above in order to transmit data to the kit we use the RX characteristic where as to receive we use to TX characteristic.
private val XpressStreamingServiceUUID = UUID.fromString("331a36f5-2459-45ea-9d95-6142f0c4b307")
private val peripheralRX = UUID.fromString("a9da6040-0823-4995-94ec-9ce41ca28833")
private val peripheralTX = UUID.fromString("a73e9a10-628f-4494-a099-12efaf72258f")
If I enable notifications on the RX I do get notified but the same never happens to the TX. To elaborate there is a Processor Board we are using that is connected to the BLE kit. So basically if I send data it is to reach the board. The Write commands have been verified to reach their destination. So transmitting data from my app as a payload works fine.
Now I am supposed to send requests to the BLE device and receive a response in return. When it comes to sending, these functions come into play:
fun write(message:String){
val bytes = BigInteger(message.replace("\\s".toRegex(), ""), 16).toByteArray()
Timber.i("Bytes value ---> ${bytes.toHexString()}")
val device = getBleDevice()
// val characteristicRX = getBleCharacteristic()
val characteristicRX = bluetoothGattRef.getService(XpressStreamingServiceUUID).getCharacteristic(
peripheralRX)
writeCharacteristic(device, characteristicRX, bytes)
}
fun requestReadValues(requestCode:String){
if(isConnected.value!!){
requestData(requestCode)
}else{
Timber.e("Make sure that you connected and paired with the desired device.")
}
}
fun sendMessage(message:String){
Timber.i("Check if isConnected = true --> ${isConnected.value}")
if(isConnected.value == true){
write(message)
}else{
Timber.e("Make sure that you connected and paired with the desired device.")
}
}
fun requestData(data: String) {
val bytes = BigInteger(data.replace("\\s".toRegex(), ""), 16).toByteArray()
Timber.i("Bytes value of request---> ${bytes.toHexString()}")
val device = getBleDevice()
val characteristic = bluetoothGattRef.getService(XpressStreamingServiceUUID).getCharacteristic(peripheralRX)
writeCharacteristic(device, characteristic, bytes)
}
These are all in a BLEConnectionManager Class. Which as the name suggests is used to manage connections with BLE devices.
I have a queuing mechanism in place like this:
#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
}
/** **/
else if(gatt.setCharacteristicNotification(characteristic, true)){
Timber.e("setCharacteristicNotification succeeded for ${characteristic.uuid}")
}
/** **/
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)
}
}
}
This is my onCharacteristicChanged method:
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic
) {
with(characteristic) {
Timber.i("Characteristic $uuid changed | value: ${value.toHexString()}")
listeners.forEach { it.get()?.onCharacteristicChanged?.invoke(gatt.device, this) }
}
}
I also have a ConnectionEventListener in the activity or fragment that is used when sending data to detect certain events:
private val connectionEventListener by lazy {
ConnectionEventListener().apply {
onDisconnect = {
runOnUiThread {
alert {
title = "Disconnected"
message = "Disconnected from device."
positiveButton("OK") { onBackPressed() }
}.show()
if(STATE_DISCONNECTED == BleConnectionManager.getConnectionStatus()){
Timber.i("Connection Status: ${BleConnectionManager.getConnectionStatus()}, therefore disconnected, ${BleConnectionManager.isConnected.value}")
Timber.i("Device Disconnected from: ${BleConnectionManager.getBleDevice().name} | ${BleConnectionManager.getBleDevice().address}")
Toast.makeText(this#MainActivity,"Please turn on BLUETOOTH",Toast.LENGTH_SHORT).show()
}else if(STATE_DISCONNECTED != BleConnectionManager.getConnectionStatus()){
Timber.e("Connection Status: ${BleConnectionManager.getConnectionStatus()}, did not disconnect")
}
}
}
onConnectionSetupComplete = {
if (STATE_CONNECTED == BleConnectionManager.getConnectionStatus()) {
Timber.i("Connection Status: ${BleConnectionManager.getConnectionStatus()}, therefore connected, ${BleConnectionManager.isConnected.value}")
Timber.i("Device Connected to: ${BleConnectionManager.getBleDevice().name} | ${BleConnectionManager.getBleDevice().address}")
}
}
onCharacteristicChanged = { _, characteristic ->
Timber.i("Value changed on ${characteristic.uuid}: ${characteristic.value.toHexString()}")
val byteArray = ByteArray(characteristic.value.size)
System.arraycopy(characteristic.value, 0, byteArray, 0, characteristic.value.size)
Timber.i("Message Received: ${byteArray.toHexString()}")
}
onNotificationsEnabled = { _, characteristic ->
Timber.i("Notify enabled on: ${characteristic.uuid}")
}
}
}
I do have to note that a log message confirming that the TX characteristic notifications are enable dis only when the services are discovered. So basically when the onServiceDiscovered function is called the "enableNotifications" is indeed used, but that's it. I don't really receive any notifications and I don't know how to proceed.
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)
// setNotification(gatt.getService(XpressStreamingServiceUUID).getCharacteristic(
// peripheralTX), true)
enableNotifications(device, characteristic)
// gatt.setCharacteristicNotification(characteristic, true)
// val desc = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG_UUID)
// desc.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
// gatt.writeDescriptor(desc)
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()
}
}
This is a screenshot message of my log when notifications are enabled on the Tx characteristic:
What this shows is that the request was sent using the RX characteristic but nothing is received using the TX. Only that notifications were enabled on the TX.
UPDATE: new screenshots of my log
I have an application that sends and receives data using BLE. Originally it was classical bluetooth but I was tasked with changing the project to BLE.
So far I have succeeded in sending data but not receiving it. The main activity contains multiple fragments. On of those is responsible for sending data where as the other sends a request and then receives a response with the data from the BLE device.
one fragment is called Parameter and the other Memory. Each fragment has a viewmodel and repository as the architecture is based on MVVM. the flow is as follows:
Parameter fragment -> View model -> repository -> DataStore class -> DataStore uses instance from BLEConnectionManager class to send the data of the corresponding parameter. Example of a function in DataStore:
fun sendToolAddressParam(data: Int){
toolAddress = data
var value = Integer.toHexString(data)
if (value.length == 1) value = "0$value"
val message = WriteCommandCodes.TOOL_ADDRESS.value + " " + value + " " + WriteCommandCodes.EXTRA2.value
BleConnectionManager.sendMessage(message)
Timber.i("Payload: $message")
}
There are also functions that request data:
fun requestToolAddress(){
BleConnectionManager.requestReadValues(ReadRequestCodes.TOOL_ADDRESS.value)
}
in the BLE class the functions are written as the following:
fun write(message:String){
val bytes = BigInteger(message.replace("\\s".toRegex(), ""), 16).toByteArray()
Timber.i("Bytes value ---> ${bytes.toHexString()}")
val device = getBleDevice()
// val characteristicRX = getBleCharacteristic()
val characteristicRX = bluetoothGattRef.getService(XpressStreamingServiceUUID).getCharacteristic(
peripheralRX)
writeCharacteristic(device, characteristicRX, bytes)
}
fun requestReadValues(requestCode:String){
if(isConnected.value!!){
write(requestCode)
}else{
Timber.e("Make sure that you connected and paired with the desired device.")
}
}
fun sendMessage(message:String){
Timber.i("Check if isConnected = true --> ${isConnected.value}")
if(isConnected.value == true){
write(message)
}else{
Timber.e("Make sure that you connected and paired with the desired device.")
}
}
Now here is my issue I want to receive data from the BLE device after I send the request, the device's documentation when it comes to BLE data exchange is here: https://docs.silabs.com/gecko-os/1/bgx/latest/ble-services
Now I have a function that supposedly receives the incoming messages but this was when classical bluetooth was used.
fun readIncomingMessages(message: String){
when{
message.startsWith(com.brainsocket.milink.data.bluetooth.ReadResponseCodes.KEY_ADDRESS.value) ->{
EventBus.getDefault().post(
ReadKeyAddressEvent(message.substring(com.brainsocket.milink.data.bluetooth.ReadResponseCodes.KEY_ADDRESS.value.length+1, com.brainsocket.milink.data.bluetooth.ReadResponseCodes.KEY_ADDRESS.value.length+3))
)
Timber.i("Message received: $message")
}
message.startsWith(com.brainsocket.milink.data.bluetooth.ReadResponseCodes.TOOL_ADDRESS.value) ->{
EventBus.getDefault().post(
ReadToolAddressEvent(message.substring(com.brainsocket.milink.data.bluetooth.ReadResponseCodes.TOOL_ADDRESS.value.length+1, com.brainsocket.milink.data.bluetooth.ReadResponseCodes.TOOL_ADDRESS.value.length+3))
)
Timber.i("Message received: $message")}
message.startsWith(com.brainsocket.milink.data.bluetooth.ReadResponseCodes.RPM_THRESHOLD.value) ->{
EventBus.getDefault().post(
ReadRPMThresholdEvent(message.substring(com.brainsocket.milink.data.bluetooth.ReadResponseCodes.RPM_THRESHOLD.value.length+1, com.brainsocket.milink.data.bluetooth.ReadResponseCodes.RPM_THRESHOLD.value.length+3))
)
Timber.i("Message received: $message")}
message.startsWith(com.brainsocket.milink.data.bluetooth.ReadResponseCodes.BACKLASH.value) ->{
EventBus.getDefault().post(
ReadBacklashEvent(message.substring(com.brainsocket.milink.data.bluetooth.ReadResponseCodes.BACKLASH.value.length+1, com.brainsocket.milink.data.bluetooth.ReadResponseCodes.BACKLASH.value.length+6))
)
Timber.i("Message received: $message")}
As you can see the Event Bus is used here, it is also used here in the DataStore:
#Subscribe(threadMode = ThreadMode.MAIN)
fun onKeyAddressEvent(event: ReadKeyAddressEvent) {
Timber.i("onKeyAddressEvent: data:${event.data}")
keyAddress = Integer.parseInt(event.data , 16)
EventBus.getDefault().post(ReadMemoryItemsEvent())
}
#Subscribe(threadMode = ThreadMode.MAIN)
fun onToolAddressEvent(event: ReadToolAddressEvent) {
Log.d(LOG_TAG, "onToolAddressEvent: data:${event.data}")
when(Integer.parseInt(event.data , 16)){
0 -> toolAddress = 1
1 -> toolAddress = 2
}
EventBus.getDefault().post(ReadMemoryItemsEvent())
}
#Subscribe(threadMode = ThreadMode.MAIN)
fun onRPMThresholdEvent(event: ReadRPMThresholdEvent) {
Log.d(LOG_TAG, "onRPMThresholdEvent: data:${event.data}")
rpmThreshold = Integer.parseInt(event.data , 16)
EventBus.getDefault().post(ReadMemoryItemsEvent())
}
#Subscribe(threadMode = ThreadMode.MAIN)
fun onReadBacklashEvent(event: ReadBacklashEvent) {
Log.d(LOG_TAG, "onReadBacklashEvent: data:${event.data}")
val data = event.data
backlash = parseGotoPos(data)
EventBus.getDefault().post(ReadMemoryItemsEvent())
}
This is in the repository:
fun getMemoryItems() : List<ModesPosItem> = listOf(
ModesPosItem(value = btDataStore.keyAddress, title = context.getString(R.string.key_address_string)),
ModesPosItem(value = btDataStore.toolAddress, title = context.getString(R.string.tool_address_string)),
ModesPosItem(value = btDataStore.rpmThreshold, title = context.getString(R.string.rpm_threshold_string)),
ModesPosItem(value = btDataStore.backlash, title = context.getString(R.string.backlash_string))
)
This is in the viewmodel:
#Subscribe(threadMode = ThreadMode.MAIN)
fun onReadMemoryItemsEvent(event: ReadMemoryItemsEvent) {
memoryItems.value = repository.getMemoryItems()
Timber.i("Memory Items [tool address, keyAddress, RPM threshold, backlash]: ${memoryItems.value.toString()}")
}
This is in the fragment:
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
}
override fun onStop() {
EventBus.getDefault().unregister(this)
super.onStop()
}
What exactly am I supposed to do to acquire the data from the BLE device?
I made an app that write and read continous stream data throw BLE connection from a bluetooth device.
The Flow i follow is the following:
Connect Gatt;
Discover Services;
Write To Characteristic;
Subscribe to Notification;
Read Characteristic from notification --> Here the EventBus post() with your data package;
Going deeper into the connection and using some code:
After you connect to the GATT you call onConnectionStateChange to listen for changes in the gatt connection:
private val gattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
val deviceAddress = gatt.device.address
if (status == BluetoothGatt.GATT_SUCCESS) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
Log.w("BluetoothGattCallback", "Successfully connected to $deviceAddress")
// NOW DISCOVER SERVICES
gatt.discoverServices()
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.w("BluetoothGattCallback", "Successfully disconnected from $deviceAddress")
}
} else {
Log.w(
"BluetoothGattCallback",
"Error $status encountered for $deviceAddress! Disconnecting..."
)
}
If the GATT is connected succesfully it will discover services.
At this step you can write to the characteristic as follow:
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
with(gatt) {
Log.w(
"BluetoothGattCallback",
"Discovered ${services.size} services for ${device.address}"
)
val msg = byteArrayOf(0x00.toByte())
val newcharacteristic = gatt!!.getService(dataUUID_service).getCharacteristic(
dataUUID_characteristic
)
newcharacteristic!!.value = msg
gatt!!.writeCharacteristic(newcharacteristic)
}
}
This will let you go on the next step, the onCharacteristicWrite listener:
override fun onCharacteristicWrite(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
val characteristic = gatt.getService(dataUUID_service).getCharacteristic(
dataUUID_characteristic
)
gatt.setCharacteristicNotification(characteristic, true)
val descriptor = characteristic!!.getDescriptor(descriptor_UUID)
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
if (descriptor != null) {
descriptor.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
gatt.writeDescriptor(descriptor)
}
}
Writing the characteristic will let you go into the onCharacteristicChanged listener that will give you back the data from the ble device and in which you can use the event bus to use your data.
override fun onCharacteristicChanged(
gatt: BluetoothGatt,
charac: BluetoothGattCharacteristic
) {
super.onCharacteristicChanged(gatt, charac)
// Log.d("CHARAC", "Characteristic Changed")
onCharacteristicRead(gatt, charac, BluetoothGatt.GATT_SUCCESS)
}
Where onCharacteristicRead should look like:
override fun onCharacteristicRead(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int
) {
with(characteristic) {
when (status) {
BluetoothGatt.GATT_SUCCESS -> {
Log.i("BluetoothGattCallback","Read characteristic $uuid:\n${value.toHexString()}" )
// value is the read value from ble device
// HERE YOU HANDE YOUR EVENT BUS, example:
val eventData: deviceListener = deviceListener(value)
EventBus.getDefault().post(eventData)
}
BluetoothGatt.GATT_READ_NOT_PERMITTED -> {
Log.e("BluetoothGattCallback", "Read not permitted for $uuid!")
}
else -> {
Log.e(
"BluetoothGattCallback",
"Characteristic read failed for $uuid, error: $status"
)
}
}
}
}
Maybe it is not the most efficent way and not the clearest code but it works like a charm.
I notice that if i write fast and continuously a characteristic value the gatt server disconnect.
I know that I have to wait until onCharacteristicWrite callback, so that's not the problem I think.
This my queue implementation, I'm using a kotlin Channel to syncronize write and read.
private var continuation: CancellableContinuation<BluetoothGattCharacteristic>? = null
private val channel = Channel<WriteOp>(1)
private suspend fun processBluetoothWrite() {
do {
val writeOp = channel.receiveOrNull()
writeOp?.apply {
try {
suspendCancellableCoroutine<BluetoothGattCharacteristic> { cont ->
continuation = cont
characteristic.value = writeOp?.value
Log.d(TAG, "Write to ${characteristic?.uuid} value ${writeOp?.value?.toHexString()}...")
if (gatt?.writeCharacteristic(characteristic) == false) {
cont.resumeWithException(Exception("Write to ${characteristic?.uuid} fails."))
}
}
} catch (ex: Exception) {
Log.e(TAG, ex.message, ex)
}
}
} while (writeOp != null)
}
override fun onCharacteristicWrite(
gatt: BluetoothGatt?,
characteristic: BluetoothGattCharacteristic?,
status: Int
) {
Log.d(TAG, "Write to ${characteristic?.uuid} value ${characteristic?.value?.toHexString()} | ${status}")
characteristic?.apply {
if (status == BluetoothGatt.GATT_SUCCESS) {
continuation?.resume(this)
} else {
continuation?.resumeWithException(Exception("Write to ${characteristic?.uuid} value ${characteristic?.value?.toHexString()} | ${status}"))
}
}
}
I need to add a delay of about 100ms in the queue processing to avoid disconnection.
UPDATE
After setting writeType as default, it seems that onCharacteristicWrite is more realistic (I used to get GATT_SUCCESS even when the device stopped communicating, so I guess it was a "virtual" state), now when the device stopped communicating it didn't get the onCharacteristicWrite callback, though after a while it is fired with status = 133.
characteristic.writeType = BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT
What does it mean?
I'm trying to implement an app for transfer some strings between ble devices (for now one device act as central and the other one as pheripheral) but without success.
This is how my peripheral (server) is set up.
Characteristic build
fun buildCharacteristic(
characteristicUUID: UUID,
): BluetoothGattCharacteristic {
var properties = BluetoothGattCharacteristic.PROPERTY_READ or
BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE or
BluetoothGattCharacteristic.PROPERTY_NOTIFY
var permission = BluetoothGattCharacteristic.PERMISSION_READ or
BluetoothGattCharacteristic.PERMISSION_WRITE
var characteristic = BluetoothGattCharacteristic(
characteristicUUID,
properties,
permission
)
return characteristic
}
service build
fun buildService(
serviceUUID: UUID,
serviceType: Int,
characteristics: List<BluetoothGattCharacteristic>
) {
bluetoothGattService = BluetoothGattService(
serviceUUID,
BluetoothGattService.SERVICE_TYPE_PRIMARY
)
for (characteristic in characteristics) {
bluetoothGattService.addCharacteristic(characteristic)
}
}
and this is how i start ble server (i omit implementation of callbacks)
fun startServer(
bleAdapter: BluetoothAdapter,
btManager: BluetoothManager,
context: Context
) {
bleAdvertiser = bleAdapter.bluetoothLeAdvertiser
bleGattServer = btManager.openGattServer(context, gattServerCallback)
bleGattServer.addService(bluetoothGattService)
var settings = AdvertiseSettings.Builder().apply {
setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
setConnectable(true)
setTimeout(0)
setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
}
var data = AdvertiseData.Builder().apply {
setIncludeDeviceName(true)
}
bleAdvertiser.startAdvertising(settings.build(), data.build(), advertiseCallback)
}
On central (client) side, when onScanResult is triggered, i try to connect with device:
fun connectToDevice(device: BluetoothDevice) {
device.connectGatt(
context,
false,
createGattCallback()
)
}
where createGattCallback() is a function return a BluetoothGattCallback object. Inside this callback, when onConnectionStateChange is called, i call service discover, and when service is discovered i try do write data to peripheral
override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
super.onServicesDiscovered(gatt, status)
if (gatt?.services != null) {
var serviceFound = false
for (service in gatt.services) {
if (service.uuid == Consts.SERVICE_UUID) {
serviceFound = true
var bluetoothGattCharacteristic = service.getCharacteristic(Consts.CHARACTERISTIC_UUID)
writeCharacteristic(
gatt,
bluetoothGattCharacteristic
)
}
}
if (!serviceFound) {
gatt.disconnect()
}
}
}
fun writeCharacteristic(
gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic
) {
var toSendString = "A random string for testing purpose only"
var toSendByteArray = toSendString.toByteArray(Charsets.UTF_8)
val chunkSize = 18
val numberOfPackets = ceil(
(toSendByteArray.size).toDouble() / chunkSize.toDouble()
)
for (i in 0 until numberOfPackets.toInt()) {
var startIndex = i * chunkSize
var endIndex = if (startIndex + chunkSize <= toSendByteArray.size) {
startIndex + chunkSize
} else {
toSendByteArray.size
}
var packet = toSendByteArray.copyOfRange(startIndex, endIndex)
characteristic.value = packet
gatt.writeCharacteristic(characteristic)
Thread.sleep(250)
}
}
My code seems not workin, on peripheral i don't receive entire string, but only the first 18 bytes. Where i'm wrong?
You need to wait for onCharacteristicWrite before you can send the next value. See Android BLE BluetoothGatt.writeDescriptor() return sometimes false.
And your sleep won't solve anything.