BLE.disconnect() Not disconnecting then crashing Boron

I am running BLE program in Central mode.
I have make a connection to my Peripheral device
When I receive a message from the Peripheral device I want to disconnect.

I have tried:
BLE.disconnectAll()
BLE.disconnect(peer);
peer.disconnect();

All of these disconnect commands have the same results:

  • The Peripheral device is disconnected, as seen from the Peripheral device.
  • The Central program does NOT recognize that it has been disconnected.
  • The evidence is in the loop(), BLE.connected() continues to read true.
  • Also after around 5 seconds the Boron’s red light starts flash and the Boron reboots.

The only way I can’t get a clean disconnect is by turning off my Peripheral device.

Any ideas?

Welcome to the community :+1:

Can you post your code - or a stripped down version that shows the issue?
When it flashes red, what blink pattern do you see (e.g. … - - - … — would be an SOS +1 hard fault).

#include "Particle.h"

// This example does not require the cloud so you can run it in manual mode or
// normal cloud-connected mode
// SYSTEM_MODE(MANUAL);

// These UUIDs were defined by Nordic Semiconductor and are now the defacto standard for
// UART-like services over BLE. Many apps support the UUIDs now, like the Adafruit Bluefruit app.
const BleUuid serviceUuid("6E400001-B5A3-F393-E0A9-E50E24DCCA9E");
const BleUuid truckServiceUuid("E20A39F4-73F5-4BC4-A12F-17D1AD07A961");
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;

BleScanResult scanResults[SCAN_RESULT_COUNT];

BleCharacteristic peerTxCharacteristic;
BleCharacteristic peerRxCharacteristic;
BlePeerDevice peer;


uint8_t txBuf[UART_TX_BUF_SIZE];
size_t txLen = 0;

const unsigned long SCAN_PERIOD_MS = 2000;
unsigned long lastScan = 0;

int led2 = D7;

String open = String("open");
String closed = String("close");
String motion = String("motion");
String disconnect = String("disconnect");

void onDataReceived(const uint8_t* data, size_t len, const BlePeerDevice& peer, void* context) {
    Serial.printlnf("*");
    // for (size_t ii = 0; ii < len; ii++) {
    //     Serial.write(data[ii]);
    // }
    // Serial.printlnf("*");
    String text = String((char *)data);
    text = text.substring(0, len);
    // Serial.printlnf(text);
    if (text.equals(disconnect)) {
        Serial.printlnf("Disconnect BLE");
        BLE.disconnectAll();
        // BLE.disconnect(peer);
        // peer.disconnect();
        // BLE.off();
        // BLE.end();
        // BLE.on();
        size_t count = BLE.scan(scanResults, SCAN_RESULT_COUNT);
    }
    
    if (text.equals(open)) {
        Serial.printlnf("Open");
        // BLE.disconnect();
    }
    
    if (text.equals(closed)) {
        Serial.printlnf("Closed");
        // BLE.disconnect();
        if (BLE.advertising()) {
            Serial.printlnf("Advertising after connected and Button!");
        }

    }
    
    if (text.equals(motion)) {
        Serial.printlnf("Motion");
        // BLE.disconnect();
    }
}

void setup() {
    Serial.begin();
    Serial.printlnf("Serial port is running!");
    Serial.printlnf("Hello  Serial Port!");
    Serial.printlnf(disconnect);
    BLE.on();
    peerTxCharacteristic.onDataReceived(onDataReceived, &peerTxCharacteristic);
    
    pinMode(led2, OUTPUT);
    // digitalWrite(led2, HIGH);
    BLE.onDisconnected(onDisconnected, NULL);
    // BLE.onConnected(onConnected, NULL);
}

    void onDisconnected(const BlePeerDevice& peer, void* context) {
        Serial.printlnf("have been disconnected from %s", (const char*)peer.address().toString());
        //bleState = DISCONNECT;
        BLE.disconnectAll();
        // waiting_tramsmision        = false;
        // Try_to_connect             = false;
        // Ble_scan                   = true;
        // Address_Valid              = false;
        
    } 
    
    void onConnected(const BlePeerDevice& peer, void* context) {
        Serial.printlnf("have been connected to %s", (const char*)peer.address().toString());
    }


int connected = 0;
int disconnected = 0;
int loopCnt = 0;
void loop() {
    // Serial.printlnf("testing");
    if (BLE.connected()) {
        if (connected == 0 ) {
            connected++;
            disconnected = 0;
            Serial.printlnf("loop connected");
            loopCnt = 0;
            digitalWrite(led2, HIGH);
        }
        if ( loopCnt % 100000 == 0) {
            Serial.printlnf("loop: %d", loopCnt / 100000);
            if (peer.connected()) {
                Serial.printlnf("peer connected");
            }
        }
        loopCnt++;
        
        // Serial.print("*");
        while (Serial.available() && txLen < UART_TX_BUF_SIZE) {
            txBuf[txLen++] = Serial.read();
            Serial.write(txBuf[txLen - 1]);
            Serial.printlnf("");
        }
        if (txLen > 0) {
            // Transmit the data to the BLE peripheral
            peerRxCharacteristic.setValue(txBuf, txLen);
            txLen = 0;
        }
    }
    else {
        if (disconnected == 0) {
            Serial.printlnf("loop disconnected");
            disconnected++;
            connected = 0;
            loopCnt = 0;
            digitalWrite(led2, LOW);
        }
        
        Serial.printlnf("disconnect loop: %d", loopCnt);
        loopCnt++;
    
        
        if (millis() - lastScan >= SCAN_PERIOD_MS) {
            // Time to scan
            lastScan = millis();

            size_t count = BLE.scan(scanResults, SCAN_RESULT_COUNT);
            if (count > 0) {
                for (uint8_t ii = 0; ii < count; ii++) {
                    // Our serial peripheral only supports one service, so we only look for one here.
                    // In some cases, you may want to get all of the service UUIDs and scan the list
                    // looking to see if the serviceUuid is anywhere in the list.
                    BleUuid foundServiceUuid;
                    size_t svcCount = scanResults[ii].advertisingData.serviceUUID(&foundServiceUuid, 1);
                    if (svcCount > 0 && (foundServiceUuid == serviceUuid || foundServiceUuid == truckServiceUuid)) {
                         digitalWrite(led2, LOW);
                        peer = BLE.connect(scanResults[ii].address);
                        if (peer.connected()) {
                            Serial.printlnf("Connected!");
                            peer.getCharacteristicByUUID(peerTxCharacteristic, txUuid);
                            peer.getCharacteristicByUUID(peerRxCharacteristic, rxUuid);

                            // Could do this instead, but since the names are not as standardized, UUIDs are better
                            // peer.getCharacteristicByDescription(peerTxCharacteristic, "tx");
                        }
                        break;
                    }
                }
            }
        }

    }
    
}

Two things:
First I recognize that all the UART stuff is not really needed.
Second, I’m am using an iPhone app to simulate the Peripheral I am building. I am thinking that because the iPhone won’t let me close the connection from Peripheral side, that could be the actual problem. Your thoughts?

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

image

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.

@ktorimaru, have you seem my post above?

Sorry, I have but have not had a chance to get back to it.

I have hit this same bug doing a similar thing.

I’ve got two Xenons connecting to an argon (os v2.1.0) and the intention was to deepsleep the xenon once it is sure the data has been received.

One of them I have told to wait for a connection from the central device, do a setValue 5 times (4 x insurance!), assume that it really was received and then the peripheral does the disconnect before going into deepsleep. It runs fine.

On the other Xenon, it wakes up, waits for a connection, but in the callback on the central argon, when sensible is data received, it does a BLE.disconnect(peer) (I have also tried peer.disconnect). The Xenon detects the disconnect, assumes it’s work is done and goes into deepsleep. However like ktorimaru, I find that the central Argon crashes after about 5 seconds. What is a coincidence is that 5 seconds is the delay to my next scan cycle, so I had thought it was failing on a scan, which never returns.

Basically one works if the peripheral does the disconnect and the other fails because it is the central device doing the disconnect from within a call back. It does disconnect and the xenon is happy. It almost looks like it doesn’t clean something in the BLE stack. The failure is flashing red with 10 flashes (Assertion failure).

Is there a work around or alternatively what is the best way to ack back to the peripheral that the characteristic data has been received?

I could keep the model of the first one and allow the xenon to send the data a few times to be sure, but then just ignore that device for 1 minute after data has been received. That does sound a bit of a dirty way to do it though.