I'm writing an embedded system which doesn't have an internet connection, so the main interaction is using BLE from an Android device. (ESP32 is using the NimBLE-Arduino library)
I have some write characteristics and some read characteristics. Each one individually works well, but when I try to read immediately after write (or vice versa), only the first callback in the ESP32 is called
ESP32 code
// Setup function
void Bluetooth::setupCharacteristic()
{
NimBLEService *pService = m_pServer->createService(SERVICE_UUID);
NimBLECharacteristic *getStationsChr = pService->createCharacteristic(GET_STATIONS_CHR_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::READ_ENC | NIMBLE_PROPERTY::READ_AUTHEN);
NimBLECharacteristic *setTimeChr = pService->createCharacteristic(SET_TIME_CHR_UUID, NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_ENC | NIMBLE_PROPERTY::WRITE_AUTHEN);
getStationsChr->setCallbacks(this);
setTimeChr->setCallbacks(this);
pService->start();
}
// Read/Write callbacks
void Bluetooth::onRead(NimBLECharacteristic* pCharacteristic){
log_i("%s", pCharacteristic->getUUID().toString().c_str());
log_i(": onRead(), value: %s",pCharacteristic->getValue().c_str());
if (m_pCallback)
{
auto value = pCharacteristic->getValue();
//m_pCallback->onMessageReceived(&value);
}
...
}
void Bluetooth::onWrite(NimBLECharacteristic* pCharacteristic) {
std::string characteristicStr = pCharacteristic->getUUID().toString();
std::string value_str(pCharacteristic->getValue().c_str());
log_i("%s: onWrite(), value: %s (%s)", characteristicStr.c_str(), pCharacteristic->getValue().c_str(), value_str.c_str());
// Process incoming value and call the callback
// ...
if (m_pCallback)
{
auto value = pCharacteristic->getValue();
//m_pCallback->onMessageReceived(&value);
}
...
}
Android code
private fun updateTime() {
val calendar = GregorianCalendar()
val ts = Instant.now().epochSecond
val timeZone = timezonesMap?.get(calendar.timeZone.id) ?: ""
val timeMessage = TimeMessage(ts, 0, timeZone)
val setTimeChar = bluetoothGatt?.getService(SERVICE_UUID)?.getCharacteristic(SET_TIME_UUID)
if (setTimeChar?.isWritable() == true) {
val timeStr = gson.toJson(timeMessage)
Log.d("updateTime", "setting time to $timeStr")
sendToCharacteristic(setTimeChar, timeStr)
}
}
// This function is used to break long message to multiple messages, this is a simplified version
// for brevity
private fun sendToCharacteristic(characteristic: BluetoothGattCharacteristic?, value: String)
{
Log.d(TAG, "Sending to write characteristic ${characteristic?.uuid} value ($value)")
characteristic?.value = value.toByteArray()
bluetoothGatt?.writeCharacteristic(characteristic)
TimeUnit.MILLISECONDS.sleep(500)
}
private fun getStations() {
Log.d("getStations", "getting stations")
val getStationsChar = bluetoothGatt?.getService(SERVICE_UUID)?.getCharacteristic(GET_STATIONS_UUID)
if (getStationsChar?.isReadable() == true) {
Log.d("getStations", "reading")
bluetoothGatt?.readCharacteristic(getStationsChar)
}
}
The issue is when calling my sync()
function
fun sync() {
Log.d("sync", "syncing")
bluetoothGatt?.apply {
if (device.bondState != BluetoothDevice.BOND_BONDED) {
device.createBond()
}
else {
updateTime()
// getStations()
}
}
}
If I uncomment the getStations
, the read callback isn't called. If I comment the updateTime
and uncomment the getStations
, the read callback is called. switching the order doesn't make a difference. First called function works, second doesn't. Also adding a 1 second delay between the calls doesn't work
CodePudding user response:
Your problem lies on the Android side.
In GATT you may only have one outstanding request. This means you must wait for the previous callback before executing a new operation. In particular, you must in this case wait for onCharacteristicWrite
on your BluetoothGattCallback
object after performing a write operation, before you can execute a next operation.
Adding a sleep doesn't work since you block the thread and hence prevent the result callback from running, whenever a response is received from the remote device.