I have done some testing with device OS 2.0.1 and 3.0.0-rc.2 and both seem to "suffer" from this behaviour.
It appears to be undocumented, but you cannot call peer.disconnect()
, BLE.disconnect()
or any of their siblings from within a BLE callback.
But this works
const int pinLED = D7;
const BleUuid serviceUuid("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
const BleUuid rxUuid("6E400002-B5A3-F393-E0A9-E50E24DCCA9E");
const BleUuid txUuid("6E400003-B5A3-F393-E0A9-E50E24DCCA9E");
const size_t UART_TX_BUF_SIZE = 20;
const size_t SCAN_RESULT_COUNT = 20;
const unsigned long SCAN_PERIOD_MS = 2000;
BleCharacteristic peerTxCharacteristic;
BleCharacteristic peerRxCharacteristic;
BlePeerDevice peer;
uint8_t txBuf[UART_TX_BUF_SIZE];
size_t txLen = 0;
unsigned long lastScan = 0;
SerialLogHandler logger(LOG_LEVEL_WARN, {{ "app", LOG_LEVEL_INFO }});
bool reqDisconnect = false;
void setup() {
pinMode(pinLED, OUTPUT);
BLE.on();
peerTxCharacteristic.onDataReceived(onDataReceived, &peerTxCharacteristic);
BLE.onDisconnected(onDisconnected, NULL);
BLE.onConnected(onConnected, NULL);
Log.info("BLE UART central ready to go");
}
uint32_t msConnectionHoldoff = 0;
void loop() {
if (BLE.connected()) {
if (reqDisconnect) { // disconnect from loop() rather than from characteristic's callback
reqDisconnect = false;
if (peer.isValid())
peer.disconnect();
return;
}
digitalWrite(pinLED, HIGH);
while (Serial.available() && txLen < UART_TX_BUF_SIZE-1) {
txBuf[txLen++] = Serial.read();
}
if (txBuf[0]) {
Log.info("sending <%s> to RxCharacteristic", txBuf);
peerRxCharacteristic.setValue(txBuf, txLen);
memset(txBuf, 0, sizeof(txBuf));
txLen = 0;
}
}
else {
digitalWrite(pinLED, LOW);
if (msConnectionHoldoff && millis() - msConnectionHoldoff < 60000) return; // after peripheral disconnected don't try to reconnect for a minute
if (millis() - lastScan >= SCAN_PERIOD_MS) {
lastScan = millis();
BleScanResult scanResults[SCAN_RESULT_COUNT];
size_t count = BLE.scan(scanResults, SCAN_RESULT_COUNT);
if (count > 0) {
for (uint8_t ii = 0; ii < count; ii++) {
BleUuid foundServiceUuid;
size_t svcCount = scanResults[ii].advertisingData().serviceUUID(&foundServiceUuid, 1);
if (svcCount > 0 && (foundServiceUuid == serviceUuid)) {
digitalWrite(pinLED, LOW);
peer = BLE.connect(scanResults[ii].address());
if (peer.connected()) {
Log.info("Connected to %s", (const char*)peer.address().toString());
peer.getCharacteristicByUUID(peerTxCharacteristic, txUuid);
peer.getCharacteristicByUUID(peerRxCharacteristic, rxUuid);
}
break;
}
}
}
}
}
}
void onDisconnected(const BlePeerDevice& peer, void* context) {
Log.info("peer %s disconnected", (const char*)peer.address().toString());
}
void onConnected(const BlePeerDevice& peer, void* context) {
Log.info("peer %s connected", (const char*)peer.address().toString());
}
void onDataReceived(const uint8_t* data, size_t len, const BlePeerDevice& peer, void* context) {
char text[len+1];
memcpy(text, data, len);
text[len] = '\0';
Log.info("TxCharacteristic: %s", text);
if (!strcmp(text, "disconnect")) {
Log.info("peer requested: DISCONNECT!");
reqDisconnect = true; // queue disconnect from loop()
digitalWrite(pinLED, LOW);
msConnectionHoldoff = millis();
return;
}
Log.warn("unexpected data: %s", text);
}
I've stripped your original code down a bit to focus on the issue at hand to show what's not working.
how not to do it
const int pinLED = D7;
const BleUuid serviceUuid("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
const BleUuid rxUuid("6E400002-B5A3-F393-E0A9-E50E24DCCA9E");
const BleUuid txUuid("6E400003-B5A3-F393-E0A9-E50E24DCCA9E");
const size_t UART_TX_BUF_SIZE = 20;
const size_t SCAN_RESULT_COUNT = 20;
const unsigned long SCAN_PERIOD_MS = 2000;
BleCharacteristic peerTxCharacteristic;
BleCharacteristic peerRxCharacteristic;
BlePeerDevice peer;
uint8_t txBuf[UART_TX_BUF_SIZE];
size_t txLen = 0;
unsigned long lastScan = 0;
SerialLogHandler logger(LOG_LEVEL_WARN, {{ "app", LOG_LEVEL_INFO }});
void setup() {
pinMode(pinLED, OUTPUT);
BLE.on();
peerTxCharacteristic.onDataReceived(onDataReceived, &peerTxCharacteristic);
BLE.onDisconnected(onDisconnected, NULL);
BLE.onConnected(onConnected, NULL);
Log.info("BLE UART central ready to go");
}
uint32_t msConnectionHoldoff = 0;
void loop() {
if (BLE.connected()) {
digitalWrite(pinLED, HIGH);
while (Serial.available() && txLen < UART_TX_BUF_SIZE-1) {
txBuf[txLen++] = Serial.read();
}
if (txBuf[0]) {
Log.info("sending <%s> to RxCharacteristic", txBuf);
peerRxCharacteristic.setValue(txBuf, txLen);
memset(txBuf, 0, sizeof(txBuf));
txLen = 0;
}
}
else {
digitalWrite(pinLED, LOW);
if (msConnectionHoldoff && millis() - msConnectionHoldoff < 60000) return; // after peripheral disconnected don't try to reconnect for a minute
if (millis() - lastScan >= SCAN_PERIOD_MS) {
lastScan = millis();
BleScanResult scanResults[SCAN_RESULT_COUNT];
size_t count = BLE.scan(scanResults, SCAN_RESULT_COUNT);
if (count > 0) {
for (uint8_t ii = 0; ii < count; ii++) {
BleUuid foundServiceUuid;
size_t svcCount = scanResults[ii].advertisingData().serviceUUID(&foundServiceUuid, 1);
if (svcCount > 0 && (foundServiceUuid == serviceUuid)) {
digitalWrite(pinLED, LOW);
peer = BLE.connect(scanResults[ii].address());
if (peer.connected()) {
Log.info("Connected to %s", (const char*)peer.address().toString());
peer.getCharacteristicByUUID(peerTxCharacteristic, txUuid);
peer.getCharacteristicByUUID(peerRxCharacteristic, rxUuid);
}
break;
}
}
}
}
}
}
void onDisconnected(const BlePeerDevice& peer, void* context) {
Log.info("peer %s disconnected", (const char*)peer.address().toString());
}
void onConnected(const BlePeerDevice& peer, void* context) {
Log.info("peer %s connected", (const char*)peer.address().toString());
}
void onDataReceived(const uint8_t* data, size_t len, const BlePeerDevice& peer, void* context) {
char text[len+1];
memcpy(text, data, len);
text[len] = '\0';
Log.info("TxCharacteristic: %s", text);
if (!strcmp(text, "disconnect")) {
Log.info("peer requested: DISCONNECT!");
peer.disconnect(); // none of these are allowed here
BLE.disconnect();
BLE.disconnect(peer);
BLE.disconnectAll();
digitalWrite(pinLED, LOW);
msConnectionHoldoff = millis();
return;
}
Log.warn("unexpected data: %s", text);
}
(tested with the nRF Connect
app on Android)
Disconnecting from the peripheral side works fine, but sending the disconnect
request from the UART peripheral to the UART central causes the delayed crash.
IMO it's also problematic that there seems to be no way to invalidate these objects once the connection to the peer was closed from the central
BleCharacteristic peerTxCharacteristic;
BleCharacteristic peerRxCharacteristic;
BlePeerDevice peer;
I guess this is the reason for the crash.
BTW, about the BLE.disconnect?(?)
functions
I'm not sure (due to grammar too) whether that comment applies to all three versions or only the first
if this only referred to line 1080 then the comment should better be to the right of the declaration and read as "This only disconnects ..." (with a separating blank line after)
or
if it referred to all of them it should better read "// These only disconnect ..."
But as it seems that these are only meant to disconnect a peripheral from the central.
It appears as if these are not intended for the central to close the connection to a peripheral from its end.
However, I find that odd. IMO the central should have some means to throw off all devices and all subscriptions with a single call without the need to traverse all peers or pull down its entire BLE object.