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.
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
}