Trying to establish a constant connection between multiple peripherals and one central

I have stored the uuids of multiple peripherals in my central and am able to switch between devices and read information from them. It happens in a round robin manner (I have used a while loop)
it first connects to one device, reads data from it, disconnects then goes to the next device.

What I am trying to establish is. Have one central device and establish a constant connection between multiple peripherals and whenever the characteristic of any of the peripherals gets updated the central should be notified and should read that data. Now I came across BLECharacteristic.subscribe() which notifies whenever the value is updated.
Where I am running into an issue is. I am unable to find a way to do this without using a while loop. A while loop causes my logic to get stuck and so it cannot connect to the other devices. even if I remove the while loop from the central, I cannot for the life of me, make sense of how to do the same for the peripheral device.

Is there a way in which a peripheral will just keep doing its own thing, inform the central whenever its data gets updated, the central gets notified and reads the data. (I still need the security, hence I am not using broadcast)

For a better understanding lets give an example
Peripheral 1:
it has a integer characteristic that switches its value between 0 and 1 every 200 ms
Peripheral 2:
it has an integer characteristic that switches its value between 2 and 3 every 500 ms

So I want my central to display the following in the serial monitor
0
1
2
0
1
...

Any help will be much appreciated.

To achieve the desired functionality without using a while loop, you can utilize event-driven programming and the event notification mechanism provided by the BLE library

That sounds great, I have not encountered any documentation or tutorial regarding that so far. Can you point me in the right direction. Also can I establish what I am trying to establish with a while loop?

I was able to establish a constant connection between 1 peripheral and 1 central and retrieve the data whenever the peripheral gets updated.

Peripheral code ->

#include <ArduinoBLE.h>

BLEService batteryService("180F");

BLEUnsignedCharCharacteristic batteryLevelChar("2A19",  // standard 16-bit characteristic UUID
    BLERead | BLENotify); // remote clients will be able to get notifications if this characteristic changes

int oldBatteryLevel = 0;  // last battery level reading from analog input
long previousMillis = 0;  // last time the battery level was checked, in ms
int batteryLevel = 1;

void setup() {
  Serial.begin(9600);    // initialize serial communication
  // while (!Serial);

  pinMode(LED_BUILTIN, OUTPUT); // initialize the built-in LED pin to indicate when a central is connected

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

    while (1);
  }

  BLE.setLocalName("BatteryMonitor");
  BLE.setAdvertisedService(batteryService); // add the service UUID
  batteryService.addCharacteristic(batteryLevelChar); // add the battery level characteristic
  BLE.addService(batteryService); // Add the battery service
  batteryLevelChar.writeValue(oldBatteryLevel); // set initial value for this characteristic

  BLE.advertise();

  Serial.println("Bluetooth® device active, waiting for connections...");
}

void loop() {
  BLEDevice central = BLE.central();

  if (central) {
    Serial.print("Connected to central: ");
    Serial.println(central.address());
    digitalWrite(LED_BUILTIN, HIGH);


    while (central.connected()) {
      long currentMillis = millis();
      if (currentMillis - previousMillis >= 2000) {
        previousMillis = currentMillis;
        updateBatteryLevel();
      }
    }
    digitalWrite(LED_BUILTIN, LOW);
    Serial.print("Disconnected from central: ");
    Serial.println(central.address());
  }
}

void updateBatteryLevel() {

  if(batteryLevel == 0){
    batteryLevel = 1;
  }else{
    batteryLevel = 0;
  }
    Serial.print("Battery Level % is now: "); // print it
    Serial.println(batteryLevel);
    batteryLevelChar.writeValue(batteryLevel);  // and update the battery level characteristic
}

Central code ->

#include <ArduinoBLE.h>

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

  // begin initialization
  if (!BLE.begin()) {
    Serial.println("starting Bluetooth® Low Energy module failed!");

    while (1);
  }

  Serial.println("Bluetooth® Low Energy Central - Peripheral Explorer");

  BLE.scan();
}

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

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

    Serial.println();

    if (peripheral.localName() == "BatteryMonitor") {
      BLE.stopScan();

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

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

      BLEService service = peripheral.service("180F");
      BLECharacteristic characteristic = service.characteristic("2a19");

      if (characteristic.canRead()) {
        characteristic.subscribe();
      }
      while(peripheral.connected()){
          characteristic.read();
            if(characteristic.valueUpdated()){
              if (characteristic.valueLength() >= 0) {
                Serial.print("value ");
                printData(characteristic.value(), characteristic.valueLength());
                Serial.println();
              }
            }
          }
      Serial.println("Disconnecting ...");
      peripheral.disconnect();
      Serial.println("Disconnected");
    }
  }
}

void printData(const unsigned char data[], int length) {
  for (int i = 0; i < length; i++) {
    unsigned char b = data[i];

    if (b < 16) {
      Serial.print("0");
    }

    Serial.print(b, HEX);
  }
}

As the central code as a while loop, I am unable to read the data for the second peripheral.

That is because that user copy/pastes from ChatGPT, so you will see no usable content.

Can you correct me if I get this wrong...

Peripheral 1 seems to work like this:
0ms = 0
200ms = 1
400ms = 0
600ms = 1

Peripheral 2 seems to work like this:
0ms = 2
500ms = 3
1000ms = 2
1500ms = 3

Combining Peripheral 1 and Peripheral 2 seems to work like this:
0ms = 0 and 2
200ms = 1
400ms = 0
500ms = 3
600ms = 1
800ms = 0
1000ms = 1 and 2
1200ms = 0
1400ms = 1
1500ms = 3

If this is correct, what is to be done on merging times, like 0ms and every 1000ms?

Indeed that is the idea I was going with, I did not mention but I thought for the multiples of 200 and 500, Like every time 1000 occurs I will put an if condition and print 4. But That is where I am having the issue. I am unable to connect two peripherals simultaneously.

My current Idea is subscribe to both the peripherals using the central device then

while(1){
//code to check if any of them gets updated and print the value
}

Does this means "do not print '1 and 2', or '0 and 2', but print 4"?

I do not understand... , can you describe this?

Yep, print 4 when it is 1000ms, Or will you recommend I go with something else.

Currently the code that I shared establishes a connection between 1 peripheral with 1 central. And this is a constant connection. I.e the central stay connected to the peripheral. And everytime the value of the peripheral is updated (it changes its own state) it advertises a notification stating that its value has been changed

As the central is subscribed to that peripheral. It gets informed about the changed. And using .valueUpdated() checks if the value has been updated. And if it has been updated. It is to print the new data using the printData function

Now I want to establish something similar with 2 peripherals and 1 central device. A constant connection between both the peripherals and the central device. And each time the value of any of the peripheral gets updated. I want the central device to print it.

Just an observation... you will never see "2"...

//timing graph shows 50ms per character
i1 = 200ms |---o---o---o---o---o---o---o---o---o---o---o---o---o---o---o
i2 = 500ms |---------o---------o---------o---------o---------o---------o...
i1&i2=1000 |4--1---0-3-1---0---4---0---1-3-0---1---4---1---0-3-1---0---4...

[edit] I made this timing wrong... (there will be no 3) see the next post

@goyalpramod

I made your counter "expanded"... to show both indexes... but they can be combined in a loop to reduce code repetition to none.

Here is a simulation of the following code...

// https://forum.arduino.cc/t/trying-to-establish-a-constant-connection-between-multiple-peripherals-and-one-central/1130776/8
// https://wokwi.com/projects/366159375120487425

// each character on this timing graph represents 50ms
// interval[0] = 200ms  |---*---*---*---*---*---*---*---*---*---*---*---*---*---*---*...
// interval[1] = 500ms  |---------*---------*---------*---------*---------*---------*...
// timer = i0*i1=1000ms |---0---1-2-0---1---4---1---0-2-1---0---4---0---1-2-0---1---4...

unsigned long interval[] = {200, 500}; // set intervals
unsigned long previousTime[] = {0, 0}; // clear timers
unsigned long currentTime; // use for all intervals

bool intervalEvent[2];
bool intervalOccurred[2];
int intervalCount[2] = {0, 2}; // starting values

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

void loop() {
  unsigned long currentTime = millis(); // update "now" time for all intervals and timers

  if (currentTime - previousTime[0] >= interval[0]) { // 200 interval
    previousTime[0] = currentTime; // event occurred, set new start time
    intervalOccurred[0] = 1; // flag for simultaneous events
  }

  if (currentTime - previousTime[1] >= interval[1]) { // 500 interval
    previousTime[1] = currentTime; // event occurred, set new start time
    intervalOccurred[1] = 1; // flag for simultaneous events
  }

  if (intervalOccurred[0] && intervalOccurred[1]) { // 200 and 500 intervals
    intervalOccurred[0] = 0; // clear flag
    if (intervalCount[0] == 0) { // if 200 count is 0
      intervalCount[0]++; // increase count
    }
    else if (intervalCount[0] == 1) { // if 200 count is 1
      intervalCount[0]--; // decrease count
    }

    intervalOccurred[1] = 0; // clear flag
    if (intervalCount[1] == 2) { // if 200 count is 2
      intervalCount[1]++; // increase count
    }
    else if (intervalCount[1] == 3) { // if 200 count is 3
      intervalCount[1]--; // decrease count
    }
    Serial.print(4);
  }

  if (intervalOccurred[0]) { // 200 interval
    intervalOccurred[0] = 0; // clear flag
    if (intervalCount[0] == 0) { // if count is 0
      Serial.print(0); // print count
      intervalCount[0]++; // increase count
    }
    else if (intervalCount[0] == 1) { // if count is 1
      Serial.print(1); // print count
      intervalCount[0]--; // decrease count
    }
  }

  if (intervalOccurred[1]) { // 500 interval
    intervalOccurred[1] = 0; // clear flag
    if (intervalCount[1] == 2) { // if count is 2
      Serial.print(2); // print count
      intervalCount[1]++; // increase count
    }
    else if (intervalCount[1] == 3) { // if count is 3
      Serial.print(3); // print count
      intervalCount[1]--; // decrease count
    }
  }
}

wow, thanks a lot. I understand it. And great simulation, it will come a lot in handy. But actually I was using 200 and 500 ms as arbitrary examples and they can be switched for anything really 125, 550 etc. I was having issues establishing a constant connection between multiple Peripherals and a central. I have changed my central code a bit and now am able to connect to two different BLEs and read data from both of them

central code

#include <ArduinoBLE.h>


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

  // begin initialization
  if (!BLE.begin()) {
    Serial.println("starting Bluetooth® Low Energy module failed!");

    while (1)
      ;
  }

  Serial.println("Bluetooth® Low Energy Central - peripheral1 Explorer");

  BLE.scan();
}

void loop() {
  // check if a peripheral1 has been discovered

  char arr[3][30] = { "180F", "181F" };
  BLE.scanForUuid(arr[0]);
  BLEDevice peripheral1 = BLE.available();

  if (peripheral1.localName() == "BatteryMonitor") {
    // BLE.stopScan();

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

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

  BLEService service1 = peripheral1.service("180F");
  BLECharacteristic characteristic1 = service1.characteristic("2a19");

  if (characteristic1.canRead()) {
    characteristic1.subscribe();
  }

  BLE.scanForUuid(arr[1]);
  BLEDevice peripheral2 = BLE.available();

  if (peripheral2.localName() == "BatteryMonitor") {
    // BLE.stopScan();

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

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

  BLEService service2 = peripheral2.service("181F");
  BLECharacteristic characteristic2 = service2.characteristic("2a19");

  if (characteristic2.canRead()) {
    characteristic2.subscribe();
  }

  // BLE.stopScan();


  while (peripheral1.connected() && peripheral2.connected()) {
    characteristic1.read();
    characteristic2.read();
    if (characteristic1.valueUpdated()) {
      if (characteristic1.valueLength() >= 0) {
        Serial.print("value ");
        printData(characteristic1.value(), characteristic1.valueLength());
        Serial.println();
      }
    }
    if (characteristic2.valueUpdated()) {
      if (characteristic2.valueLength() >= 0) {
        Serial.print("value ");
        printData(characteristic2.value(), characteristic2.valueLength());
        Serial.println();
      }
    }
  }
  peripheral1.disconnect(); // if we do not add this we face random disconnection
  peripheral2.disconnect();
}

void printData(const unsigned char data[], int length) {
  for (int i = 0; i < length; i++) {
    unsigned char b = data[i];

    if (b < 16) {
      Serial.print("0");
    }

    Serial.print(b, HEX);
  }
}

But the connection time is still extremely unstable, I believe it's because I am not doing BLE.stopScan() after a BLE has been discovered and it's trying to establish a connection. But so far this has been the only way I have been able to connect to multiple BLEs at the same time.

What output are you seeing from the Serial.print()s?

0
1
2
1
3
0

It's eradic. Nothing concrete. I believe there is some loss of information.

image

It does not seem to follow the pattern of the simulation (012014102104), but your pattern is repeating (1203).

That is true, But this is the case for only some time. After a while like 5 min or so. there is some random loss and the pattern changes for a while, and then it comes back to this.

Yeh, it does not seem to follow the simulation. I believe it has something to do with my code.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.