bluetooth-lowenergyesp32mesh-network

(painlessmesh) ESP32 boards not connecting to the mesh when also doing BLE Scan


I am trying to build a mesh of 3 ESP32 using the library painlessMesh, which seems a very smart tool (https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/esp-wifi-mesh.html)

Each board should continuously perform a quick BLE scan (≈2 sec) and then broadcast the results through the mesh until the root node, in form of a string. The root node should then process the scan data.

I am using a task scheduler in order to manage 2 main tasks (perform the scan, broadcast results) using TaskScheduler (https://github.com/arkhipenko/TaskScheduler)

To get started I followed this great guide from RandomNerds (https://randomnerdtutorials.com/esp-mesh-esp32-esp8266-painlessmesh/): it allows me to get the 3 boards working fine, connecting very quickly and broadcasting a simple "hello from board X" message.

Anyway, when I introduce the function to perform a 2-second BLE scan, suddenly the boards are no more capable of recognizing each other. When powered, they do not connect to the mesh (Suppose that board 1 and board 2 are now loaded with BLE scan code: they won't find each other. If board 3 is loaded with simple "hello" code, it may recognize board 1 and connect, but not board 2).

Very rarely they manage to connect and work as they are supposed to, but I don't know why. After a few seconds they disconnect, anyway.

Each board is loaded with the exact same code, except for the string BOARD_NAME (ranges 1 to 3) which is used to identify the board.

sorry for the Italian comments

#include "painlessMesh.h" //libreria per usare il protocollo ESP-WIFI-MESH

//direttive per la scansione BLE
#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>

//dettagli della mesh che stiamo realizzando. la rete si occupa da sola di
//mandare in giro i messaggi per tutta la rete.

#define   MESH_PREFIX     "meshName" //nome della rete
#define   MESH_PASSWORD   "password123" //password per la rete
#define   MESH_PORT       5555 //porta tcp sul quale far girare il server. default: 5555.

String BOARD_NAME = "1"; //nome/numero della board

int scanTime = 2; //BLE Scan duration (seconds)
BLEScan* pBLEScan; //oggetto BLEScan
String jsonString;
bool scanCompleted;


Scheduler userScheduler;
painlessMesh  mesh;  //oggetto rete

class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
    void onResult(BLEAdvertisedDevice advertisedDevice) {
      //Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str());
    }
};

// User stub
void transmitData() ; //prototipo di sendMessage(), da non rimuovere
void scan(); //prototipo di scan(), da non rimuovere

//TASKS
Task transmit( TASK_SECOND * 4 , TASK_FOREVER, &transmitData ); 
Task bleScan(TASK_SECOND * 3, TASK_FOREVER, &scan); 



void scan() {

  scanCompleted = false;

  Serial.print(F("Board "));
  Serial.print(BOARD_NAME);
  Serial.print(F(" is scanning...\n"));

  BLEScanResults foundDevices = pBLEScan->start(scanTime, false); //salva i risultati della scansione in foundDevices

//creazione array JSON
  StaticJsonDocument<1024> scanResultsJson;
  JsonArray devicesArray = scanResultsJson.to<JsonArray>();

  for (int i = 0; i < foundDevices.getCount(); i++) {         //salva nell-iesima posizione dell'array l'i-esimo device (mac, RSSI)
    BLEAdvertisedDevice device = foundDevices.getDevice(i);
    JsonObject deviceJson = devicesArray.createNestedObject();
    deviceJson["mac_address"] = device.getAddress().toString();
    deviceJson["rssi"] = device.getRSSI();
    deviceJson["boardName"] = BOARD_NAME; //inseriamolo per capire quale board ha fatto il rilevamento
    
    //Serial.printf("Device %d: MAC Address: %s, RSSI: %d\n", i+1, device.getAddress().toString().c_str(), device.getRSSI());
  }

  //"stringhifichiamo" il JSON per spedirlo con la painlessMesh
  serializeJson(scanResultsJson, jsonString);
  scanCompleted = true;

  //Serial.print("JsonString: ");
  //Serial.println(jsonString);

}

void transmitData() {
  

  if (scanCompleted){
    Serial.println(BOARD_NAME + " is trying to broadcast...");
    mesh.sendBroadcast(jsonString);
    jsonString = "";
    pBLEScan->clearResults();
  }
   
}

// Callback necessari per fare in modo che la rete sia sempre up to date
void receivedCallback( uint32_t from, String &msg ) {
  Serial.println("Nuovo messaggio: " + msg);
}

//questa funzione viene eseguita ogni volta che un nuovo nodo si connette alla rete
void newConnectionCallback(uint32_t nodeId) {
    Serial.printf("Nuovo nodo, nodeId = %u\n", nodeId);
}


//questa viene eseguita quando cambia la connessione con un nodo (un nodo entra o lascia la rete)
void changedConnectionCallback() {
  Serial.printf("Cambiamento di rete\n");
}

//questa viene eseguita quando la rete aggiusta il timing, così tutti sono in Sync
void nodeTimeAdjustedCallback(int32_t offset) {
    Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(),offset);
}

void setup() {
  Serial.begin(115200);

//Scegliere il messaggio di Debug che più aggrada sua maestà sviluppatore

//mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on
  mesh.setDebugMsgTypes( ERROR | STARTUP | CONNECTION | GENERAL );  // set before init() so that you can see mesh startup messages
  mesh.init( MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT ); //inizializzazione della mesh con i parametri prima definiti

  //assegniamo ad ogni evento la relativa funzione.
  mesh.onReceive(&receivedCallback);
  mesh.onNewConnection(&newConnectionCallback);
  mesh.onChangedConnections(&changedConnectionCallback);
  mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);


  //BLE Functions
  BLEDevice::init("");
  pBLEScan = BLEDevice::getScan(); //create new scan
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster
  pBLEScan->setInterval(100); //distanza temporale tra due scansioni, in ms
  pBLEScan->setWindow(99);  // finestra di tempo in cui deve scansionare, tra due scansioni. dev'essere <= al valore della riga sopra per chiare ragioni

  
  //TASKS
  userScheduler.addTask(transmit); //aggiungiamo allo scheduler il compito di trasmettere il msg
  userScheduler.addTask(bleScan);
  bleScan.enable(); 
  transmit.enable();



}

void loop() {
  mesh.update(); //mantiene la mesh in esecuzione ed anche lo scheduler
}

I strongly believe that BLEScanResults foundDevices = pBLEScan->start(scanTime, false); inside "scan" Task is the source of all the evil, because when that Task is not enabled everything works fine. I know that this specific line "stops" the code while making the scan, but I don't get why it causes connection issues nor how to solve it.

I have already checked:

Hope someone can help, googling I found other people having similar connections troubles along the past years.

Thank everyone so much in advance.


Solution

  • Following @MichaelKotzjan advice in the comment, I got everything working quite fine. The problem was actually the scan instruction which blocked the code flow, and I managed to solve it by implementing the scanCompleteCB() function you can see in the code.

    The following code performs a BLE scan and then broadcast it over the mesh. Each board is able to connect kinda quickly to the other boards (I made a test with 3 boards).

    The code:

    #include "painlessMesh.h"
    
    //direttive per la scansione BLE
    #include <BLEDevice.h>
    #include <BLEUtils.h>
    #include <BLEScan.h>
    #include <BLEAdvertisedDevice.h>
    #include <string>
    
    
    
    
    #define   MESH_PREFIX     "meshName" //nome della rete
    #define   MESH_PASSWORD   "password123" //password per la rete
    #define   MESH_PORT       5555 
    
    
    /* GLOBAL VARIABLES */
    
    
    int scanTime = 2; //BLE Scan duration (seconds). non deve essere maggiore del primo parametro di Task bleScan, altrimenti c'è un conflitto.
    String jsonString;
    Scheduler userScheduler;
    painlessMesh  mesh;  //oggetto rete
    String BOARD_NAME = String(mesh.getNodeId()); //nome/numero della board
    
    
    
    class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
      
        void onResult(BLEAdvertisedDevice advertisedDevice) {
    
          //Serial.printf("Advertised Device: %s \n", advertisedDevice.toString().c_str());
    
        }
    
    };
    
    // Prototypes
    void transmitData() ;
    void scan();
    
    
    //TASKS
    Task transmit( TASK_SECOND * 3 , TASK_FOREVER, &transmitData ); 
    Task bleScan(TASK_SECOND * 3, TASK_FOREVER, &scan); 
    
    static void scanCompleteCB(BLEScanResults foundDevices) {
    
        printf("Scan complete!\n We found %d devices\n", foundDevices.getCount());
    
      StaticJsonDocument<1024> scanResultsJson;
      JsonArray devicesArray = scanResultsJson.to<JsonArray>();
    
      for (int i = 0; i < foundDevices.getCount(); i++) {         //salva nell-iesima posizione dell'array l'i-esimo device (mac, RSSI)
    
        BLEAdvertisedDevice device = foundDevices.getDevice(i);
        JsonObject deviceJson = devicesArray.createNestedObject();
        deviceJson["mac_address"] = device.getAddress().toString();
        deviceJson["rssi"] = device.getRSSI();
        deviceJson["boardName"] = mesh.getNodeId(); //inseriamolo per capire quale board ha fatto il rilevamento
        
        //Serial.printf("Device %d: MAC Address: %s, RSSI: %d\n", i+1, device.getAddress().toString().c_str(), device.getRSSI());
      }
    
      //"stringhifichiamo" il JSON per spedirlo con la painlessMesh
      serializeJson(scanResultsJson, jsonString);
      devicesArray.clear();
    
      
    
      //Serial.print("JsonString: ");
      //Serial.println(jsonString);
    
        foundDevices.dump();
    }
    
    static void run() {
      /*Serial.print(F("Board "));
      Serial.print(BOARD_NAME);
      Serial.print(F(" is scanning...\n"));*/
    
        printf("Async Scanning sample starting\n");
    
        BLEDevice::init("");
        BLEScan* pBLEScan = BLEDevice::getScan();
        pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks(), false);
        pBLEScan->setActiveScan(true);
      pBLEScan->setInterval(200); //distanza temporale tra due scansioni, in ms
      pBLEScan->setWindow(199);  // finestra di tempo in cui deve scansionare, tra due scansioni. dev'essere <= al valore della riga sopra per chiare ragioni
    
        Serial.println("About to start scanning for "+ String(scanTime)+ " seconds");
    
        pBLEScan->start(scanTime, scanCompleteCB);
        printf("Now scanning in the background ... scanCompleteCB() will be called when done.\n");
    
    }
    
    void scan() {
    
      Serial.println("Entered scan Task");
      run();
      
    }
    
    
    void transmitData() {
      
      Serial.println(BOARD_NAME + " is trying to broadcast...");
      mesh.sendBroadcast(jsonString);
        
       
    }
    
    // Callback necessari per fare in modo che la rete sia sempre up to date
    void receivedCallback( uint32_t from, String &msg ) {
    
      Serial.println("Nuovo messaggio: " + msg);
    
      char lastChar = msg.charAt(msg.length() - 1);
      String BOARD_ARRIVED_ID = String(lastChar);
    
      int i = BOARD_ARRIVED_ID.toInt();
    
      /*
      
      Serial.print("BOARD_ARRIVED_ID int = ");
      Serial.println(i);
      Serial.print("BOARD_ARRIVED_ID string = ");
      Serial.println(BOARD_ARRIVED_ID);
    
      */
      Serial.println("Nuovo messaggio ricevuto da BOARD " + BOARD_ARRIVED_ID + " ");
    
    }
    
    
    
    //questa funzione viene eseguita ogni volta che un nuovo nodo si connette alla rete
    void newConnectionCallback(uint32_t nodeId) {
        Serial.printf("Nuovo nodo, nodeId = %u\n", nodeId);
    }
    
    
    //questa viene eseguita quando cambia la connessione con un nodo (un nodo entra o lascia la rete)
    void changedConnectionCallback() {
      Serial.printf("Cambiamento di rete\n");
    }
    
    //questa viene eseguita quando la rete aggiusta il timing, così tutti sono in Sync
    void nodeTimeAdjustedCallback(int32_t offset) {
        Serial.printf("Adjusted time %u. Offset = %d\n", mesh.getNodeTime(),offset);
    }
    
    void setup() {
      Serial.begin(115200);
    
    //Scegliere il messaggio di Debug che più aggrada sua maestà sviluppatore
    
    //mesh.setDebugMsgTypes( ERROR | MESH_STATUS | CONNECTION | SYNC | COMMUNICATION | GENERAL | MSG_TYPES | REMOTE ); // all types on
      mesh.setDebugMsgTypes( ERROR | STARTUP | CONNECTION | GENERAL );  // set before init() so that you can see mesh startup messages
      mesh.init( MESH_PREFIX, MESH_PASSWORD, &userScheduler, MESH_PORT ); //inizializzazione della mesh con i parametri prima definiti
      //assegniamo ad ogni evento la relativa funzione.
      mesh.onReceive(&receivedCallback);
      mesh.onNewConnection(&newConnectionCallback);
      mesh.onChangedConnections(&changedConnectionCallback);
      mesh.onNodeTimeAdjusted(&nodeTimeAdjustedCallback);
    
    
      userScheduler.addTask(transmit); //aggiungiamo allo scheduler il compito di trasmettere il msg
      userScheduler.addTask(bleScan);
      bleScan.enable(); 
      transmit.enable();
    
    
    
    }
    
    
    void loop() {
      mesh.update(); //mantiene la mesh in esecuzione ed anche lo scheduler
    }