NimBLE-Arduino
NimBLE-Arduino copied to clipboard
Server and Client on one device, core dump on using uninitalized NimBLEAdvertisedDevice
I'm getting a core panic when running code that connects to a BLE server. I am using NimBLE 1.4.1, ESP32-S3, Arduino Version: 2.1.1-nightly-20230627, and derived by code from https://github.com/h2zero/NimBLE-Arduino/blob/release/1.4/examples/NimBLE_Server/NimBLE_Server.ino and https://github.com/h2zero/NimBLE-Arduino/blob/release/1.4/examples/NimBLE_Client/NimBLE_Client.ino.
My code crashes when executing:
if (!pClient->connect(advDevice))
{
/** Created a client but failed to connect, don't need to keep it as it has no data */
NimBLEDevice::deleteClient(pClient);
Serial.println("Failed to connect, deleted client");
return false;
}
I'm going to keep debugging to solve the problem, and I will post the solution here. Still, if anyone wants to offer me ideas, solutions, and criticism I would be glad to receive it. Thanks, in advance.
Here is what I see in the Serial Monitor:
CALLIOPE-F4: Server advertising started
CALLIOPE-F4: BLE Advertised Device found: Name: CALLIOPE-B4, Address: 60:55:f9:f5:c3:b5, serviceUUID: 7d9029fe-48d5-49e0-b9ad-8fd7dac70354
New client created
a
b
c
advDevice == nothing
CALLIOPE-F4: Failed to connect to server
CALLIOPE-F4: BLE Advertised Device found: Name: CALLIOPE-B4, Address: 60:55:f9:f5:c3:b5, serviceUUID: 7d9029fe-48d5-49e0-b9ad-8fd7dac70354
Guru Meditation Error: Core 1 panic'ed (LoadProhibited). Exception was unhandled.
Core 1 register dump:
PC : 0x40056f17 PS : 0x00060530 A0 : 0x82007fc8 A1 : 0x3fcebe30
A2 : 0x3fcebe49 A3 : 0x00000000 A4 : 0x00000007 A5 : 0x3fcebe49
A6 : 0x02c982d8 A7 : 0x00ffffff A8 : 0x60023000 A9 : 0x60023040
A10 : 0x1d87af98 A11 : 0x000fffff A12 : 0x00000003 A13 : 0x3fca4c40
A14 : 0x00060023 A15 : 0x00000003 SAR : 0x0000000a EXCCAUSE: 0x0000001c
EXCVADDR: 0x00000000 LBEG : 0x40056f5c LEND : 0x40056f72 LCOUNT : 0xffffffff
Backtrace: 0x40056f14:0x3fcebe30 |<-CORRUPTED
ELF file SHA256: 9c739da8436c0751
Rebooting...
ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0xc (RTC_SW_CPU_RST),boot:0x28 (SPI_FAST_FLASH_BOOT)
Saved PC:0x420c07e2
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fce3808,len:0x44c
load:0x403c9700,len:0xbe4
load:0x403cc700,len:0x2a38
entry 0x403c98d4
BLE.h contents:
#ifndef _BLE_
#define _BLE_
#include "Arduino.h"
#include <WiFi.h>
#include "config.h"
#include "secrets.h"
#include <NimBLEDevice.h>
class BLE
{
public:
BLE();
void begin();
void loop();
String getHeading();
void setHeading( String heading );
String getHeadingValue();
bool connectToServer();
private:
std::string devname;
int mycounter;
unsigned long serverWaitTime;
unsigned long clientWaitTime;
String heading;
};
#endif // _BLE_
BLE.cpp contents:
Reflections, distributed entertainment device
Repository is at https://github.com/frankcohen/ReflectionsOS
Includes board wiring directions, server side components, examples, support
Licensed under GPL v3 Open Source Software
(c) Frank Cohen, All rights reserved. [email protected]
Read the license in the license.txt file that comes with this code.
I originally tried using the ESP32 blue BLE library. I ran out of memory. Switched to
NimBLE-Arduino, an Arduino fork of Apache NimBLE, a replacement open-source BLE library
https://github.com/h2zero/NimBLE-Arduino
https://www.arduino.cc/reference/en/libraries/nimble-arduino/
https://h2zero.github.io/esp-nimble-cpp/md__migration_guide.html#autotoc_md46
Todo:
Make this work among multiple Reflections devices
Balance the active scanning for battery life
*/
#include "BLE.h"
BLE::BLE(){}
// Server
static NimBLEServer* pServer;
static NimBLECharacteristic* pHeadingCharacteristic;
static boolean serverConnected;
static boolean clientConnected;
static String devicename;
static NimBLERemoteCharacteristic* pRemoteCharacteristic;
static NimBLEAdvertisedDevice* myDevice;
static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static String remotename;
static NimBLEAdvertisedDevice* advDevice;
static uint32_t scanTime = 0; /** 0 = scan forever */
static BLEUUID serviceUUID( SERVICE_UUID_CALLIOPE );
static BLEUUID charUUID( CHARACTERISTIC_UUID_HEADING );
// Client
String BLE::getHeading()
{
return heading;
}
void BLE::setHeading( String _heading )
{
heading = _heading;
}
String BLE::getHeadingValue()
{
String myc = String( mycounter++ ) + heading;
std::string mys = myc.c_str();
return myc;
}
/** Callback to process the results of the last scan or restart it */
void scanEndedCB(NimBLEScanResults results){
Serial.println("Scan Ended");
}
/**
* Scan for BLE servers and find the first one that advertises the service we are looking for.
*/
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
/**
* Called for each advertising BLE server.
*/
void onResult(BLEAdvertisedDevice* advertisedDevice)
{
//Serial.print( devicename );
//Serial.print(": BLE advertised device found: ");
//Serial.println(advertisedDevice->toString().c_str());
// We have found a device, let us now see if it contains the service we are looking for.
if ( advertisedDevice -> haveServiceUUID() && advertisedDevice -> isAdvertisingService( serviceUUID ) )
{
remotename = advertisedDevice->toString().c_str();
Serial.print( devicename );
Serial.print(": BLE Advertised Device found: ");
Serial.println( remotename );
BLEDevice::getScan()->stop();
myDevice = advertisedDevice;
doConnect = true;
doScan = true;
}
}
};
class ServerCallbacks: public NimBLEServerCallbacks {
void onConnect(NimBLEServer* pServer)
{
Serial.print( devicename );
Serial.println(": Server connected");
NimBLEDevice::startAdvertising();
};
void onDisconnect(NimBLEServer* pServer)
{
Serial.print( devicename );
Serial.println(": Client disconnected, starting advertising");
NimBLEDevice::startAdvertising();
};
void onMTUChange(uint16_t MTU, ble_gap_conn_desc* desc) {
Serial.print( devicename );
Serial.printf(": BLE MTU updated: %u for connection ID: %u\n", MTU, desc->conn_handle);
};
};
class MyClientCallback: public BLEClientCallbacks {
void onConnect(BLEClient* pclient)
{
Serial.print( devicename );
Serial.println(": onConnect");
}
void onDisconnect(BLEClient* pclient) {
connected = false;
Serial.print( devicename );
Serial.println(": onDisconnect");
}
};
/** None of these are required as they will be handled by the library with defaults. **
** Remove as you see fit for your needs */
class ClientCallbacks: public NimBLEClientCallbacks {
void onConnect(NimBLEClient* pClient) {
Serial.println("Connected");
/** After connection we should change the parameters if we don't need fast response times.
* These settings are 150ms interval, 0 latency, 450ms timout.
* Timeout should be a multiple of the interval, minimum is 100ms.
* I find a multiple of 3-5 * the interval works best for quick response/reconnect.
* Min interval: 120 * 1.25ms = 150, Max interval: 120 * 1.25ms = 150, 0 latency, 60 * 10ms = 600ms timeout
*/
pClient->updateConnParams(120,120,0,60);
};
void onDisconnect(NimBLEClient* pClient) {
Serial.print(pClient->getPeerAddress().toString().c_str());
Serial.println(" Disconnected - Starting scan");
NimBLEDevice::getScan()->start(scanTime, scanEndedCB);
};
/** Called when the peripheral requests a change to the connection parameters.
* Return true to accept and apply them or false to reject and keep
* the currently used parameters. Default will return true.
*/
bool onConnParamsUpdateRequest(NimBLEClient* pClient, const ble_gap_upd_params* params) {
if(params->itvl_min < 24) { /** 1.25ms units */
return false;
} else if(params->itvl_max > 40) { /** 1.25ms units */
return false;
} else if(params->latency > 2) { /** Number of intervals allowed to skip */
return false;
} else if(params->supervision_timeout > 100) { /** 10ms units */
return false;
}
return true;
};
/********************* Security handled here **********************
****** Note: these are the same return values as defaults ********/
uint32_t onPassKeyRequest(){
Serial.println("Client Passkey Request");
/** return the passkey to send to the server */
return 123456;
};
bool onConfirmPIN(uint32_t pass_key){
Serial.print("The passkey YES/NO number: ");
Serial.println(pass_key);
/** Return false if passkeys don't match. */
return true;
};
/** Pairing process complete, we can check the results in ble_gap_conn_desc */
void onAuthenticationComplete(ble_gap_conn_desc* desc){
if(!desc->sec_state.encrypted) {
Serial.println("Encrypt connection failed - disconnecting");
/** Find the client with the connection handle provided in desc */
NimBLEDevice::getClientByID(desc->conn_handle)->disconnect();
return;
}
};
};
/** Handler class for server characteristic actions */
class CharacteristicCallbacks: public NimBLECharacteristicCallbacks {
void onRead(NimBLECharacteristic* pCharacteristic){
Serial.print( devicename );
Serial.print(": BLE server ");
Serial.print(pCharacteristic->getUUID().toString().c_str());
Serial.print(": onRead(), value: ");
Serial.println(pCharacteristic->getValue().c_str());
};
void onWrite(NimBLECharacteristic* pCharacteristic) {
Serial.print( devicename );
Serial.print( ": ");
Serial.print(pCharacteristic->getUUID().toString().c_str());
Serial.print(": onWrite(), value: ");
Serial.println(pCharacteristic->getValue().c_str());
};
/** Called before notification or indication is sent,
* the value can be changed here before sending if desired.
*/
void onNotify(NimBLECharacteristic* pCharacteristic) {
Serial.print( devicename );
Serial.println(": Sending notification to clients");
};
/** The status returned in status is defined in NimBLECharacteristic.h.
* The value returned in code is the NimBLE host return code.
*/
void onStatus(NimBLECharacteristic* pCharacteristic, Status status, int code) {
String str = ("Notification/Indication status code: ");
str += status;
str += ", return code: ";
str += code;
str += ", ";
str += NimBLEUtils::returnCodeToString(code);
Serial.print( devicename );
Serial.print( ": ");
Serial.println(str);
};
void onSubscribe(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc, uint16_t subValue) {
String str = "Client ID: ";
str += desc->conn_handle;
str += " Address: ";
str += std::string(NimBLEAddress(desc->peer_ota_addr)).c_str();
if(subValue == 0) {
str += " Unsubscribed to ";
}else if(subValue == 1) {
str += " Subscribed to notfications for ";
} else if(subValue == 2) {
str += " Subscribed to indications for ";
} else if(subValue == 3) {
str += " Subscribed to notifications and indications for ";
}
str += std::string(pCharacteristic->getUUID()).c_str();
Serial.println(str);
};
};
/** Notification / Indication receiving handler callback */
void notifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify){
std::string str = (isNotify == true) ? "Notification" : "Indication";
str += " from ";
/** NimBLEAddress and NimBLEUUID have std::string operators */
str += std::string(pRemoteCharacteristic->getRemoteService()->getClient()->getPeerAddress());
str += ": Service = " + std::string(pRemoteCharacteristic->getRemoteService()->getUUID());
str += ", Characteristic = " + std::string(pRemoteCharacteristic->getUUID());
str += ", Value = " + std::string((char*)pData, length);
Serial.println(str.c_str());
}
/** Create a single global instance of the callback class to be used by all clients */
static ClientCallbacks clientCB;
bool BLE::connectToServer()
{
NimBLEClient* pClient = nullptr;
/** Check if we have a client we should reuse first **/
if(NimBLEDevice::getClientListSize())
{
/** Special case when we already know this device, we send false as the
* second argument in connect() to prevent refreshing the service database.
* This saves considerable time and power.
*/
pClient = NimBLEDevice::getClientByPeerAddress(advDevice->getAddress());
if(pClient)
{
if(!pClient->connect(advDevice, false))
{
Serial.print( devicename );
Serial.println(": Reconnect failed");
return false;
}
Serial.print( devicename );
Serial.println(": Reconnected client");
}
else
{
pClient = NimBLEDevice::getDisconnectedClient();
}
}
/** No client to reuse? Create a new one. */
if( !pClient )
{
if(NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS)
{
Serial.println("Max clients reached - no more connections available");
return false;
}
pClient = NimBLEDevice::createClient();
Serial.println("New client created");
Serial.println("a");
pClient->setClientCallbacks(&clientCB, false);
/** Set initial connection parameters: These settings are 15ms interval, 0 latency, 120ms timout.
* These settings are safe for 3 clients to connect reliably, can go faster if you have less
* connections. Timeout should be a multiple of the interval, minimum is 100ms.
* Min interval: 12 * 1.25ms = 15, Max interval: 12 * 1.25ms = 15, 0 latency, 51 * 10ms = 510ms timeout
*/
Serial.println("b");
pClient->setConnectionParams(12,12,0,51);
/** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */
pClient->setConnectTimeout(5);
Serial.println("c");
if ( advDevice )
{
Serial.println( "advDevice == something" );
}
else
{
Serial.println( "advDevice == nothing" );
return false;
}
if (!pClient->connect(advDevice))
{
/** Created a client but failed to connect, don't need to keep it as it has no data */
NimBLEDevice::deleteClient(pClient);
Serial.println("Failed to connect, deleted client");
return false;
}
Serial.println("d");
if(!pClient->isConnected()) {
if (!pClient->connect(advDevice)) {
Serial.println("Failed to connect");
return false;
}
}
Serial.print("Connected to: ");
Serial.println(pClient->getPeerAddress().toString().c_str());
Serial.print("RSSI: ");
Serial.println(pClient->getRssi());
/** Now we can read/write/subscribe the charateristics of the services we are interested in */
NimBLERemoteService* pSvc = nullptr;
NimBLERemoteCharacteristic* pChr = nullptr;
NimBLERemoteDescriptor* pDsc = nullptr;
Serial.println("e");
pSvc = pClient->getService( serviceUUID );
if( pSvc )
{
pChr = pSvc->getCharacteristic( charUUID );
if(pChr)
{
if(pChr->canRead()) {
Serial.print(pChr->getUUID().toString().c_str());
Serial.print(" Value: ");
Serial.println(pChr->readValue().c_str());
}
/** registerForNotify() has been deprecated and replaced with subscribe() / unsubscribe().
* Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=false.
* Unsubscribe parameter defaults are: response=false.
*/
if(pChr->canNotify()) {
//if(!pChr->registerForNotify(notifyCB)) {
if(!pChr->subscribe(true, notifyCB)) {
/** Disconnect if subscribe failed */
pClient->disconnect();
return false;
}
}
else if(pChr->canIndicate())
{
/** Send false as first argument to subscribe to indications instead of notifications */
//if(!pChr->registerForNotify(notifyCB, false)) {
if( !pChr -> subscribe(false, notifyCB) )
{
/** Disconnect if subscribe failed */
pClient->disconnect();
return false;
}
}
}
}
else
{
Serial.print( devicename );
Serial.println(": Service not found.");
}
Serial.println("Done with this device!");
return true;
}
}
static CharacteristicCallbacks chrCallbacks;
void BLE::begin()
{
devname = "CALLIOPE-";
std::string mac = WiFi.macAddress().c_str();
devname.append( mac.substr( 15, 2 ) );
NimBLEDevice::init( devname );
devicename = devname.c_str();
advDevice = nullptr;
mycounter = 0;
clientWaitTime = millis();
clientConnected = false;
serverWaitTime = millis();
serverConnected = false;
// Server set-up
pServer = NimBLEDevice::createServer();
pServer->setCallbacks( new ServerCallbacks() );
NimBLEService* pService = pServer -> createService( SERVICE_UUID_CALLIOPE );
pHeadingCharacteristic = pService -> createCharacteristic( CHARACTERISTIC_UUID_HEADING, NIMBLE_PROPERTY::READ );
pHeadingCharacteristic -> setCallbacks( &chrCallbacks );
pHeadingCharacteristic -> setValue( getHeadingValue() );
pService->start();
NimBLEAdvertising* pAdvertising = NimBLEDevice::getAdvertising();
pAdvertising -> addServiceUUID( pService -> getUUID() );
pAdvertising->setScanResponse(true);
pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue
pAdvertising->setMaxPreferred(0x12);
NimBLEDevice::startAdvertising();
Serial.print( devicename );
Serial.println(": Server advertising started");
// Client set-up
// Retrieve a Scanner and set the callback we want to use to be informed when we
// have detected a new device. Specify that we want active scanning and start the
// scan to run for 5 seconds.
BLEScan* pBLEScan = BLEDevice::getScan();
pBLEScan -> setAdvertisedDeviceCallbacks( new MyAdvertisedDeviceCallbacks() );
pBLEScan -> setInterval(1349);
pBLEScan -> setWindow(449);
pBLEScan -> setActiveScan(true);
pBLEScan -> start(5, false);
}
void BLE::loop()
{
// Server
if ((millis() - serverWaitTime) > 5000)
{
serverWaitTime = millis();
String myc = String( mycounter++ ) + heading;
std::string mys = myc.c_str();
pHeadingCharacteristic->setValue( mys );
}
// Client
if ((millis() - clientWaitTime) > 5000)
{
clientWaitTime = millis();
// If the flag "doConnect" is true then we have scanned for and found the desired
// BLE Server with which we wish to connect. Now we connect to it. Once we are
// connected we set the connected flag to be true.
if ( doConnect == true )
{
if ( connectToServer() )
{
Serial.print( devicename );
Serial.println(": Connected to BLE server.");
doScan = false;
doConnect = true;
}
else
{
Serial.print( devicename );
Serial.println(": Failed to connect to server" );
doConnect = true;
doScan = true;
}
}
if(doScan)
{
NimBLEDevice::getScan()->start(0); // this is just eample to start scan after disconnect, most likely there is better way to do it in arduino
doScan = false;
}
}
}
This is a part of my Reflection's open source entertainment platform making project https://github.com/frankcohen/ReflectionsOS. The code needs to have server and client on the same device as the device may be in a room full of other devices, and there's no way to declare a single device to be a beacon or server. -Frank
Found it. Line 34: static NimBLEAdvertisedDevice* myDevice; needs to be named advDevice, and line 39 deleted, and line 93 needs to be advDevice = advertisedDevice; Of course, now I get a different error:
E NimBLEClient: A connection to 60:55:f9:f5:c3:b5 already exists
Digging into this next. I love C/C++, not.
@frankcohen Did you figure out your new error? I am having some problem
@frankcohen Did you figure out your new error? I am having some problem
I found that I was making some basic mistakes on how to initialize the libraries objects. The working code is in BLE.cpp and BLE.h in https://github.com/frankcohen/ReflectionsOS/tree/main/Devices/BlueFish/ReflectionsOfFrank. Glad to get your feedback and changes.
Thanks!
On Wed, Dec 27, 2023 at 2:27 PM Frank Cohen @.***> wrote:
@frankcohen https://github.com/frankcohen Did you figure out your new error? I am having some problem
I found that I was making some basic mistakes on how to initialize the libraries objects. The working code is in BLE.cpp and BLE.h in https://github.com/frankcohen/ReflectionsOS/tree/main/Devices/BlueFish/ReflectionsOfFrank. Glad to get your feedback and changes.
— Reply to this email directly, view it on GitHub https://github.com/h2zero/NimBLE-Arduino/issues/561#issuecomment-1870570829, or unsubscribe https://github.com/notifications/unsubscribe-auth/AFKFNLVXPKEP6GTRD2Y5P53YLRZBDAVCNFSM6AAAAAAZY2QPSOVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQNZQGU3TAOBSHE . You are receiving this because you commented.Message ID: @.***>