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

59 views Asked by At

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:

  • Same SSID/PASSWORD for each mesh
  • Enough distance so they don't interfere during connection

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

Thank everyone so much in advance.

1

There are 1 answers

2
Simone Colombo On BEST ANSWER

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
}