Fast transmission of data via BLE to multiple cilents

Hi

I am in possession of a number of Arduino Nano BLE Sense-boards. I am on a project, where I have to transmit data (about 20 bytes) from a master to a couple of clients (both parts is Arduino Nano BLE Sense-boards) wireless via BLE. It should be like af broadcast, where all clients receives the same information, and it needs to bee fast. I want to broadcast a package every 50-100 milliseconds.

I think I will make the master as i peripheral device which advertises, and the clients (centrals) reads the package from the service characteristics.

I have made a few test, but it seems like I have to make a connection between the master and the client, and it seems like I only can make a connection one client at the time. The disconnection and connection takes too long to transmit the package every 50-100 millisecond.

Can anybody tell me, whether it is possible to connect multiple clients at the time, and how to do it?

Hello esben1703,

It should be possible to connect multiple centrals to one peripheral as can be seen in the diagram on the BLE library: ArduinoBLE - Arduino Reference. Would it possible to upload the code you have written for both the central and peripheral using the </> button in the reply box?

Also, have you tried setting the specific package characteristic so that it will notify changes using BLENotify? That way your centrals can subscribe to the peripheral and await changes to the characteristic.

Thanks,
Matt

Thank you very much for your answer!

Your suggestion to use the notify function helped me a lot.
I have made a new test program and it work fine, but I can only connect one central at the time. I have tried to save the connected central in a variable called "central_old", and then search for an other central, but this does not work.

Any suggestions how to connect multiple centrals?

Here is the code for the peripheral. I hope, it is not too confusing. Note that it is a test program - not all names makes sense:

#include <ArduinoBLE.h>

//Service
BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1214"); // BLE LED Service

//Characteristics
BLEByteCharacteristic switchCharacteristic("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify); 
BLEUnsignedLongCharacteristic timestamp("19B10002-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify);

BLEDevice central;
BLEDevice central_old;

unsigned long t;

byte LED_value = 0;

void setup() {
  // Start terminal
  Serial.begin(115200);

  BLE.begin();

  // set advertised local name and service UUID:
  BLE.setLocalName("LED");
  BLE.setAdvertisedService(ledService);
    
  // add the characteristic to the service
  ledService.addCharacteristic(switchCharacteristic);
  ledService.addCharacteristic(timestamp);
    
  // add service
  BLE.addService(ledService);

  // Set characteristics
  switchCharacteristic.writeValue(LED_value);
  timestamp.writeValue(millis());

  BLE.advertise();
  
  Serial.println("Peripheral on");

  t = millis();
}

void loop() {
  if (Serial.read() == 'n')
  {
    Serial.println();
    Serial.println("Connect new device");
  
    if ((!central) || (central == central_old))
    {
      Serial.print("Connection enabled");
      unsigned long connect_time = millis();
      while (((!central) || (central == central_old)) && ((millis() - connect_time) <= 10000))
      {
        central = BLE.central();
        Serial.print("*");
      }
      Serial.println();
  
      if (central)
      {
        central_old = central;
        
        Serial.print("Connected to central: ");
        // print the central's MAC address:
        Serial.println(central.address());
    
        // while the central is still connected to peripheral:
        Serial.print("Connecting...");
        while (!central.connected()) 
        {
          Serial.print(".");
        }
        Serial.println(" ");
        Serial.println("Connected");
        t = millis();
        while ((central.connected()) && ((millis() - t) < 5000))  
        {}
      }
      else
      {
        Serial.println("Connection failed");
      }
    }  
  }
  
  if ((millis() - t) >= 5000)
  {
    // Set characteristics
    LED_value++;
    switchCharacteristic.writeValue(LED_value);
    timestamp.writeValue(millis());
  
    Serial.println();
    byte value_to_read;
    switchCharacteristic.readValue(value_to_read);
    Serial.print("switchCharacteristic: ");
    Serial.println(value_to_read);
  
    unsigned long timestamp_value;
    timestamp.readValue(timestamp_value);
    Serial.print("timestamp: ");
    Serial.println(timestamp_value);

    t = millis();
  }
}

I will also show the code for my centrals:

#include <ArduinoBLE.h>

BLEDevice peripheral;

BLECharacteristic ledCharacteristic;
BLECharacteristic peripheral_timestamp;

unsigned long timestamp;
unsigned long BLE_timestamp;

void setup() {
  // Start terminal
  Serial.begin(115200);

  // Init BLE
  timestamp = millis();
  BLE.begin();
  
  Serial.print("Time to init BLE: ");
  Serial.println(millis() - timestamp);
  Serial.println("");

  // Start scan for peripheral service
  BLE.scanForUuid("19B10000-E8F2-537E-4F6C-D104768A1214");

  // check if a peripheral has been discovered
  Serial.print("Scaning...");
  BLE_timestamp = millis();
  while((peripheral == 0) && ((millis() - BLE_timestamp) < 1000)) // Scan - timeout after 250 ms
  {
    peripheral = BLE.available();
    Serial.print(".");
  }
  Serial.println();
  Serial.println();

  // Check for discovered device
  if (peripheral)
  {
    // discovered a peripheral, print out address, local name, and advertised service
    Serial.print("Found ");
    Serial.print(peripheral.address());
    Serial.print(" '");
    Serial.print(peripheral.localName());
    Serial.print("' ");
    Serial.print(peripheral.advertisedServiceUuid());
    Serial.println();
    
    // Check for correct device discovered
    if (peripheral.localName() == "LED") 
    {
      // stop scanning
      BLE.stopScan();
      
      // Connect to device
      Serial.println("Connecting...");
      if (peripheral.connect()) 
      {
        Serial.println("Connected");
        
        // Discover attributes
        Serial.println("Discovering attributes...");
        if (peripheral.discoverAttributes()) 
        {
          Serial.println("Attributes discovered");
          
          // Connect to characteristics
          ledCharacteristic = peripheral.characteristic("19b10001-e8f2-537e-4f6c-d104768a1214");
          peripheral_timestamp = peripheral.characteristic("19b10002-e8f2-537e-4f6c-d104768a1214");
          
          //Error handling for connection to characteristics
          Serial.println("ledCharacteristic:");
          if (!ledCharacteristic) 
            Serial.println("      NOT accessable"); 
          else
            Serial.println("      Accessable");

          if (ledCharacteristic.canSubscribe())
            Serial.println("      Subscribable");
          else
            Serial.println("      NOT subscribable");

          if (ledCharacteristic.subscribe())
            Serial.println("      Subscription succeed");
          else
            Serial.println("      Subscription failed!");

          Serial.println("peripheral_timestamp:");
          if (!peripheral_timestamp) 
            Serial.println("      NOT accessable");
          else
            Serial.println("      Accessable");

          if (peripheral_timestamp.canSubscribe())
            Serial.println("      Subscribable");
          else
            Serial.println("      NOT subscribable");

          if (peripheral_timestamp.subscribe())
            Serial.println("      Subscription succeed");
          else
            Serial.println("      Subscription failed!");

          Serial.println();
        } 
      }
    }
  }

  BLE_timestamp = millis();
}

void loop() {
  if (peripheral.connected())
  {
    //Check for updated values
    if (ledCharacteristic.valueUpdated())
    {
      Serial.println("ledCharacteristic: Value updated");
      byte LED_value;
      ledCharacteristic.readValue(LED_value);
      Serial.print("LED_value: ");
      Serial.println(LED_value);
      Serial.println("");
    }

    if (peripheral_timestamp.valueUpdated())
    {
      Serial.println("peripheral_timestamp: Value updated");
      unsigned long peripheral_timestamp_value;
      peripheral_timestamp.readValue(peripheral_timestamp_value);
      Serial.print("peripheral_timestamp_value: ");
      Serial.println(peripheral_timestamp_value);
      Serial.println("");
    }
  }
}

Thank you

Hello esben1703,

So I did a bit more digging and found out that part of the problem you are having is down to the fact that when a connection occurs the BLE will stop advertising. The other part is that your code currently "hangs" in the while connected loop.

To overcome this I used something called an Event Handler. These are used to, as the name indicates, handle certain events when they happen by executing functions. You can look into these more in the BLE library

I tested this using the following code for the Peripheral (on a Nano 33 Sense) which just switches the LED every 100ms and updates the characteristics to match:

#include <ArduinoBLE.h>

BLEService ledService("19B10000-E8F2-537E-4F6C-D104768A1214");  // BLE LED Service

BLEByteCharacteristic switchCharacteristic("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify);
const int ledPin = LED_BUILTIN;  // pin to use for the LED
unsigned long last_time = 0;

void ConnectHandler(BLEDevice central) {
  // central connected event handler
  Serial.print("Connected event, central: ");
  Serial.println(central.address());
  BLE.advertise();
}

void DisconnectHandler(BLEDevice central) {
  // central disconnected event handler
  Serial.print("Disconnected event, central: ");
  Serial.println(central.address());
  BLE.advertise();
}

void setup() {
    Serial.begin(9600);
    while (!Serial)
        ;

    // set LED pin to output mode
    pinMode(ledPin, OUTPUT);

    // begin initialization
    if (!BLE.begin()) {
        Serial.println("starting BLE failed!");

        while (1)
            ;
    }
    BLE.setEventHandler(BLEConnected, ConnectHandler);
    BLE.setEventHandler(BLEDisconnected, DisconnectHandler);
    // set advertised local name and service UUID:
    BLE.setLocalName("LED");
    BLE.setAdvertisedService(ledService);

    // add the characteristic to the service
    ledService.addCharacteristic(switchCharacteristic);

    // add service
    BLE.addService(ledService);
    // set the initial value for the characeristic:
    switchCharacteristic.writeValue(0);

    // start advertising
    BLE.advertise();

    Serial.println("BLE LED Peripheral");
}

void loop() {
  unsigned long current_time = millis();
  BLE.poll();
  if(current_time - last_time > 100){ 
    last_time = current_time;
    //Sensor Code/Functions would go here
    if (!switchCharacteristic.value()) {
        switchCharacteristic.writeValue(1);
        Serial.println("LED on");
        digitalWrite(ledPin, HIGH);
    } else {
        switchCharacteristic.writeValue(0);
        Serial.println("LED off");
        digitalWrite(ledPin, LOW);
    }
  } 
}

Then the central code (on a Nano 33 IoT) look like this:

#include <ArduinoBLE.h>

void listenLed(BLEDevice peripheral) {
  // connect to the peripheral
  Serial.println("Connecting ...");

  if (peripheral.connect()) {
    Serial.println("Connected");
  } else {
    Serial.println("Failed to connect!");
    return;
  }

  // discover peripheral attributes
  Serial.println("Discovering attributes ...");
  if (peripheral.discoverAttributes()) {
    Serial.println("Attributes discovered");
  } else {
    Serial.println("Attribute discovery failed!");
    peripheral.disconnect();
    return;
  }

  // retrieve the LED characteristic
  BLECharacteristic ledCharacteristic = peripheral.characteristic("19b10001-e8f2-537e-4f6c-d104768a1214");

  if (!ledCharacteristic) {
    Serial.println("Peripheral does not have LED characteristic!");
    peripheral.disconnect();
    return;
  }
  if (!ledCharacteristic.subscribe()) {
    Serial.println("Subscription failed!");
    peripheral.disconnect();
    return;
  }
  while (peripheral.connected()) {
    if (ledCharacteristic.valueUpdated()) {
      byte v = 0; 
      ledCharacteristic.readValue(v);
      Serial.print("LED Value: ");
      Serial.println(v);
    }
  }
  Serial.println("Peripheral disconnected");
}

void setup() {
  Serial.begin(9600);
  while (!Serial);

  // configure the button pin as input

  // initialize the BLE hardware
  BLE.begin();

  Serial.println("BLE Central - LED control");

  // start scanning for peripherals
  BLE.scanForUuid("19b10000-e8f2-537e-4f6c-d104768a1214");
}

void loop() {
  // check if a peripheral has been discovered
  BLEDevice peripheral = BLE.available();

  if (peripheral) {
    // discovered a peripheral, print out address, local name, and advertised service
    Serial.print("Found ");
    Serial.print(peripheral.address());
    Serial.print(" '");
    Serial.print(peripheral.localName());
    Serial.print("' ");
    Serial.print(peripheral.advertisedServiceUuid());
    Serial.println();

    if (peripheral.localName() != "LED") {
      return;
    }

    // stop scanning
    BLE.stopScan();

    listenLed(peripheral);

    // peripheral disconnected, start scanning again
    BLE.scanForUuid("19b10000-e8f2-537e-4f6c-d104768a1214");
  }
}

Now I didn't actually have two IoT's on hand but this does work with one IoT and my phone using nRF Connect (a great app for testing BLE stuff). I was able to get the IoT and phone to subscribe to the changes of the LED state.

However, this does occasionally crash. This is I think in part due to a yet to be fixed bug in the Sense which causes its BLE Module to crash sometimes when it disconnects and means it has to be reset manually.

Anyway, I hope that helps, I put a comment where I think your sensor code would go. Obviously, this set up would be sensing all the time if you know how many centrals you will need you can use the event handler to count before starting. Let me know if you have any questions

Thanks
Matt

1 Like

Hi Matt

Thank you very much for the code. I have read about the eventhandler, but I didn't really understand why to use it, but now I see.

It works now. It is not very reliable, because it crashes sometimes, but I now try to analyze the problem.

Thank you again. I have used a lot of hours on this problem - and you saved me more hours.

-Esben