Closed KevinMartinezC closed 4 months ago
Hello, Here are the results of my investigation.
I think the main issue is with the MTU. Seems like your switch is requesting high MTU on it's own from the peripheral side. Have a look at this log entry:
Notification received from 0734594a-a8e7-4b1a-a6b1-cd5243059a57, value: (0x) 01-01-74-41-41-41-41-42-33-4E-7A-61-43-31-79-63-32-45-41-41-41-41-44-41-51-41-42-41-41-41-42-41-51-43-35-6F-6C-7A-79-30-62-46-78-35-6E-61-63-77-34-50-6C-66-50-44-78-36-75-76-6D-47-6F-2F-35-30-77-42-6A-66-2B-49-58-35-44-5A-30-76-68-53-69-6A-34-4C-7A-46-47-38-35-2F-49-65-67-75-68-50-66-34-38-4B-47-32-6B-4F-7A-44-39-57-5A-34-6B-31-38-74-72-41-61-43-75-54-76-44-57-4D-41-61-50-31-77-6C-63-38-78-74-63-67-34-32-6A-54-6E-56-78-76-51-76-64-70-55-47-67-6B-33-51-31-75-2B-4C-56-46-45-35-6E-36-35-38-38-39-32-6F-4F-37-67-42-30-75-6E-69-4B-61-55-58-30-39-47-44-54-53-59-73-78-6E-50-36-4F-6B-69-2B-42-52-73-6F-65-73-68-61-58-30-68-44-41-63-54-2F-58-44-4A-78-68-67-33-41-51-63-69-42-6D-50-6E-55-53-49-64-69-46-7A-57-4B-75-48-6D-64
On the other hand, the same notification that you get from the AP is only 20 bytes long. 20 bytes is the packet length with the default MTU=23 (max length is 20 bytes). Because of that, the firmware may experience some kind of issue (you try to send a lot more) and it stops.
Either make sure you set the same MTU value on both switch and AP or request it from Android. Add the following:
override fun initialize() {
requestMtu(517).enqueue()
// [...]
initialize()
method are first added to the initialization queue before they are started being executed. However, in your code you have the following:
enableNotifications(readChar).done {
sendCredits()
}.enqueue()
// other requests...
The sendCredits()
also enqueues a request, but it will be performed AFTER the initialization is complete. It's not a part
of the initialization, as it's enqueued outside of initiazlize()
. It does work but placing it inside that method and in the middle may be confusing. I would recommend just moving it after the last readCharacteristic
or, if you want to make sure it only happens when notifications are enabled, at least adding some comment.
readCharacteristic(serialNumberCharacteristic).with(ledCallback).enqueue()
I was using the android library and i want to update it to use nordic for a better user but i am still facing the issue with the AP
gatt.requestMtu(508)
configureMTU() - device: XX:XX:XX:XX:EB:C4 mtu: 508
onConfigureMTU() - Device=B9:BC:00:23:EB:C4 mtu=517 status=0
MTU changed to: 517
Reading characteristic 00002a26-0000-1000-8000-00805f9b34fb
gatt.readCharacteristic(00002a26-0000-1000-8000-00805f9b34fb)
Read Response received from 00002a26-0000-1000-8000-00805f9b34fb, value: (0x) 32-2E-30-6A
Reading characteristic 00002a25-0000-1000-8000-00805f9b34fb
gatt.readCharacteristic(00002a25-0000-1000-8000-00805f9b34fb)
Read Response received from 00002a25-0000-1000-8000-00805f9b34fb, value: (0x) 62-63-62-39-32-33-30-30-63-34-65-38
Reading characteristic 00002a25-0000-1000-8000-00805f9b34fb
gatt.readCharacteristic(00002a25-0000-1000-8000-00805f9b34fb)
Read Response received from 00002a25-0000-1000-8000-00805f9b34fb, value: (0x) 62-63-62-39-32-33-30-30-63-34-65-38
Reading characteristic 00002a26-0000-1000-8000-00805f9b34fb
gatt.readCharacteristic(00002a26-0000-1000-8000-00805f9b34fb)
get serial number bcb92300c4e8
Read Response received from 00002a26-0000-1000-8000-00805f9b34fb, value: (0x) 32-2E-30-6A
gatt.setCharacteristicNotification(0734594a-a8e7-4b1a-a6b1-cd5243059a57, true)
setCharacteristicNotification() - uuid: 0734594a-a8e7-4b1a-a6b1-cd5243059a57 enable: true
Enabling notifications for 0734594a-a8e7-4b1a-a6b1-cd5243059a57
gatt.writeDescriptor(00002902-0000-1000-8000-00805f9b34fb, value=0x01-00)
get firmware 2.0j
Request timed out
Disconnecting...
gatt.disconnect()
cancelOpen() - device: XX:XX:XX:XX:EB:C4
onClientConnectionState() - status=0 clientIf=7 device=B9:BC:00:23:EB:C4
[Callback] Connection state changed with status: 0 and new state: 0 (DISCONNECTED)
Disconnected
gatt.close()
close()
unregisterApp() - mClientIf=7
conextion fail XX:XX:XX:XX:EB:C4 and status -1
override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean {
val switchService = gatt.getService(BlinkySpec.SWITCH_SERVICE)
firmwareRevisionCharacteristic = switchService?.getCharacteristic(BlinkySpec.FIRMWARE_CHARACTERISTIC)
serialNumberCharacteristic = switchService?.getCharacteristic(BlinkySpec.SERIAL_CHARACTERISTIC)
val sppService = gatt.getService(SPP_UUID)
readChar = sppService?.getCharacteristic(READ_UUID)
txCtlChar = sppService?.getCharacteristic(TX_CTL_UUID)
writeChar = sppService?.getCharacteristic(WRITE_UUID)
return switchService != null && firmwareRevisionCharacteristic != null && serialNumberCharacteristic != null &&
sppService != null && readChar != null && txCtlChar != null && writeChar != null
}
The part to get the key stop working on AP while it works on the switch . In AP it works sometimes in android 14 but it does not work in android 12 while switch works in both
@philips77 could you provide me an example how it would be the correct way to enable the notifications and read and write properties correctly please in this case i just want to keep listened the notifications from readChar
I commented your code. It should work. If it works with the switch, but doesn't work with AP, you have an issue on the AP side, not here.
@OptIn(ExperimentalCoroutinesApi::class)
override fun initialize() {
requestMtu(517).enqueue() // <- 517 instead of 508, as this is the value requested by Android 14 (it actually ignores the parameter)
// Enable notifications for the button characteristic.
setNotificationCallback(txCtlChar)
enableNotifications(txCtlChar).enqueue()
readCharacteristic(readChar).enqueue() // <- Do you need to do this? You ignore the received value here
// Read the initial value of the write characteristic.
readCharacteristic(writeChar).enqueue() // <- Same here.
enableNotifications(firmwareRevisionCharacteristic).enqueue() // <- You never set notification callback for this char.
// Read the initial value of the button characteristic.
readCharacteristic(firmwareRevisionCharacteristic).with(buttonCallback).enqueue() // <- button callback?
// Read the initial value of the LED characteristic.
readCharacteristic(serialNumberCharacteristic).with(ledCallback).enqueue() // <- LED callback?
readCharacteristic(serialNumberCharacteristic).with { device, data -> // <- you read serial again, this time with another callback
val serialNumber = data.getStringValue(0)
serialNumberValue = serialNumber
Log.wtf("kevin", "get serial number $serialNumber")
// Handle serial number value
}.enqueue()
readCharacteristic(firmwareRevisionCharacteristic).with { device, data -> // <- You read this char for the second time, now with a different callback
val firmwareRevision = data.getStringValue(0)
firmware = firmwareRevision
Log.wtf("kevin", "get firmware $firmwareRevision")
// Handle firmware revision value
}.enqueue()
setNotificationCallback(readChar).with { device, data ->
val value = data.value
Log.wtf("kevin","get data value $value")
if (value != null) {
collectDataKey(value)
}
}
enableNotifications(readChar)
.fail { device, status ->
Log.wtf("kevin","connection fail $device and status $status")
}
.done {
sendCredits() // <- Mind, that you'll get `onDeviceReady()` before the result to this method, as it's called after initialization is complete
}.enqueue()
sendCredits() // <- You could move the method here, so it's part of the initialization
}
Thank you! I will update it with the comments you added and test it
@philips77 I get this error message when i try to connect an AP using the nRf CONNECT APP
Setting the value of the mtu requestMTU(517).enqueue() it makes it works on android 14 and 13 but it does not work on android 12
Do i need to do a different implementation for android 12?
No, it should work the same way for any Android 5+ version.
Regarding that error, seems like Android tries to do service discovery or connection param update using Group Type which your device may not support. Perhaps that may be ignored..?
Is it a way to disable it? or that would be an issue ?
When I try to connect with Android 12 i get these logs:
unregisterApp() - mClientIf=8
Connecting...
gatt = device.connectGatt(autoConnect = false, TRANSPORT_LE, LE 1M)
connect() - device: B9:BC:00:23:1F:14, auto: false
registerApp()
registerApp() - UUID=cfba8dbc-6cd7-476b-976b-c1d8c9f0801a
onClientRegistered() - status=0 clientIf=8
onClientConnectionState() - status=0 clientIf=8 device=B9:BC:00:23:1F:14
[Callback] Connection state changed with status: 0 and new state: 2 (CONNECTED)
Connected to B9:BC:00:23:1F:14
wait(300)
Discovering services...
gatt.discoverServices()
discoverServices() - device: B9:BC:00:23:1F:14
Request timed out
Disconnecting...
gatt.disconnect()
cancelOpen() - device: B9:BC:00:23:1F:14
onClientConnectionState() - status=0 clientIf=8 device=B9:BC:00:23:1F:14
[Callback] Connection state changed with status: 0 and new state: 0 (DISCONNECTED)
Disconnected
gatt.close()
close()
unregisterApp() - mClientIf=8
I solved it! I increased the connection timeout for Android 12 version. I really appreciate your comments and help
Great to hear that!
@philips77 I have a question for example I have two devices and I want to connect each one when I click but they are in the same ViewModel
so it will not navigate to another screen. So, when I connect the first one, it works perfect but when I try to connect the second one after finishing adding the first device, I am not able to connect even if after setting up the first one I disconnect it. Should I stop the scan and start doing another scan again?
this is my current implementation, should i add another method to be able to disconnect and connect a new device ?
override fun release() {
// Cancel all coroutines.
scope.cancel()
val wasConnected = isReady
// If the device wasn't connected, it means that ConnectRequest was still pending.
// Cancelling queue will initiate disconnecting automatically.
// If the device was connected, we have to disconnect manually.
if (wasConnected) {
disconnect().enqueue()
}
}
for the seconde device when i get into here detect it as connect it and it does complete the process. It is like still has notifications of the previous one
setNotificationCallback(readChar).with { device, data ->
val value = data.value
if (value != null) {
collectDataKey(value, address = device.address)
}
}
private fun collectDataKey(value: ByteArray?, address: String) {
value?.size?.let {
rxCredits += it
}
if (value != null) {
rxData += value
Timber.tag("ALTA-CHANGED-KEY").d("Value ${value.size}")
}
Timber.tag("ALTA-CHANGED-KEY").d("RXData ${rxData.size}")
if (rxData.size < THREE_INT) return
val total: Int =
rxData.copyOfRange(ONE_INT, THREE_INT).fold(ZER0_INT) { a, b -> (a shl 8) + b.toInt() }
if (rxData.size < total + THREE_INT) return
val pktType = rxData[ZER0_INT]
val payload = rxData.copyOfRange(THREE_INT, THREE_INT + total)
rxData = rxData.copyOfRange(THREE_INT + total, rxData.size)
when (pktType.toUByte()) {
ALTA_PUB_KEY -> {
pubKey = payload
val pubKeyStr = pubKey.decodeToString()
pupkey = pubKeyStr
Timber.tag(ALTA_BLE_TAG).wtf("get pup key %s", pubKeyStr)
if (pubKey.isNotEmpty()) {
pubKeyValue.value = pubKeyStr
}
Timber.tag("ALTA-CHANGED-KEY").d(pubKeyStr)
}
ALTA_STATE -> {
val state = payload[ZER0_INT].toInt()
val inet = if (state and (ONE_INT shl ZER0_INT) != ZER0_INT) INET else EMPTY_STRING
val mesh = if (state and (ONE_INT shl ONE_INT) != ZER0_INT) MESH else EMPTY_STRING
val cloud = if (state and (ONE_INT shl TWO_INT) != ZER0_INT) CLOUD else EMPTY_STRING
val setup =
if (state and (ONE_INT shl THREE_INT) != ZER0_INT) SET_UP else EMPTY_STRING
if (state and (ONE_INT shl THREE_INT) != ZER0_INT) {
Log.wtf("kevin","completed")
isConnected.value = true
}
Timber.tag("ALTA-STATUS").d("state: $inet $mesh $cloud $setup")
}
else -> {}
}
}
but i am resetting the values before connect into another device
override fun resetValues (){
disableNotifications(readChar).enqueue()
rxCredits = 0x1000
pupkey = null
txData = ByteArray(0)
pubKey = ByteArray(0)
isConnected.value = false
firmwareVersion.value = null
serialNumber.value = null
pubKeyValue.value = null
onServicesInvalidated()
release()
}
override fun onServicesInvalidated() {
serialNumberCharacteristic = null
firmwareRevisionCharacteristic = null
txCtlChar = null
readChar = null
writeChar = null
}`
@philips77 do i need to use another method to clear the notifications?
Instead of reseting values, like you do, call:
disconnect().enqueue()
This will disconnect the device and will call onServicesInvalidated()
automatically when done. There you may reset your values. I think you're now only disconnecting when the ViewModel
gets closed, so when you leave the screen, not when you want to connect to another device.
Also, no need to disable notifications, as you're disconnecting after all.
@philips77 I solved it. Thanks. I have another question using that library is able to connect to multiple device at the same time when it is discover by the scanner one by one ?
Yes, it is possible. You need to have one BleManager
instance per device. I think modern phones support up to 6 simultaneous connections.
Thank you. I will try it!
I am currently using the Android-BLE-library for Kotlin, trying to get the data for switches and APs. When I try to connect a switch, the notifications keep listening, and I get the firmware, serial number, and key. However, when I try it with the AP, I only get the firmware and serial number because it seems the notification stops before getting the key.
These are my logs from the switch:
These are for the AP:
This is my code: