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.