Nano 33 BLE Peripheral --> Central Comms

Ultimately, I want to get two Nano 33 BLEs to act as peripherals that send their respective battery levels to a third Nano 33 BLE acting as the central. My first step though is to just get one peripheral to send its battery level to the central. For the peripheral, i used the Arduino BLE example BatteryMonitor.ino. I can see the battery levels in the Serial Monitor and I can connect my iPhone to the peripheral and I can see the battery levels there too. So all seems good on the peripheral side of things.

The Central, however, is where I'm running into an issue (or issues). I added the Central's code below.

The Serial Monitor on the Central side prints the following (minus the "-" marks):


BatteryMonitor
180f
0
Found BatteryMonitor Name
Batt Level 0

So, it appears to have found the Local Name and Service ID but the number of Characteristics appears to be zero and the Batt Level always shows "0" regardless of what is showing the on the peripheral side.

I fully and freely admit I'm probably doing a number of things wrong here but cannot/have not been able to sort out the issue(s). Once I figure this out, I will likely be back for advice on how to add another peripheral to the mix.

#include <ArduinoBLE.h>

// BLE Battery Service
BLEService batteryService("180F");

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

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

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

    while (1);
  }

  BLE.scan();

}

void loop() {
  // wait for a BLE central

  BLEDevice peripheral = BLE.available();
 
  if (peripheral) {
    Serial.println(peripheral.localName());
    Serial.println(peripheral.advertisedServiceUuid());
    Serial.println(peripheral.characteristicCount());

    if (peripheral.localName() == "BatteryMonitor") {
      Serial.println("Found BatteryMonitor Name");
      peripheral.connect();
      unsigned int batLevel = batteryLevelChar.read();
    Serial.print("Batt Level ");
      Serial.println(batLevel);
    }
  }

}

I assume you are using the ArduinoBLE library. If so, which Central example have you tried or are working form.

The reason being is that you did not use the checks peripheral.hasLocalName() and if peripheral.hasAdvertisedServiceUuid() for some reason. Those methods are useful for problem solving.

Yes, I am using the AdruinoBLE library. I've copy/pasted the Central code I'm using - I'm not using one of the example Central examples from the library - I didn't see one that did exactly what I needed so I pieced together something I thought would work (which includes the two has* items you noted). It isn't working.

This is what I get:

Local Name: BatteryMonitor
Service UUIDs: 180f
0 characteristics discovered

and here's the code...

#include <ArduinoBLE.h>

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

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

    while (1);
  }

  BLE.scan();
  Serial.println("Scanned Scanning - 1");

}

void loop() {
  // wait for a BLE central

  BLEDevice peripheral = BLE.available();

  peripheral.connect();
  if (peripheral) {
    if (peripheral.hasLocalName()) {
      Serial.print("Local Name: ");
      Serial.println(peripheral.localName());
    }
    if (peripheral.hasAdvertisedServiceUuid()) {
      Serial.print("Service UUIDs: ");
      for (int i = 0; i < peripheral.advertisedServiceUuidCount(); i++) {
        Serial.print(peripheral.advertisedServiceUuid());
        Serial.print(" ");
      }
      Serial.println();
    }
    
    int characteristicCount = peripheral.characteristicCount();
    Serial.print(characteristicCount);
    Serial.println(" characteristics discovered");
      for (int i = 0; i < characteristicCount; i++) {
        Serial.print(peripheral.characteristic(i));
        Serial.print(" ");
      }
      Serial.println();
  
    if (peripheral.hasCharacteristic("2A19")) {
      Serial.println("Peripheral has battery level characteristic");
    } else {
      Serial.println("Peripheral does not have battery level characteristic");
    }
    Serial.println("  ");
  }
}

Here is the peripheral code - it is the ArduinoBLE library's BatterMonitor unchanged:

/*
  Battery Monitor

  This example creates a BLE peripheral with the standard battery service and
  level characteristic. The A0 pin is used to calculate the battery level.

  The circuit:
  - Arduino MKR WiFi 1010, Arduino Uno WiFi Rev2 board, Arduino Nano 33 IoT,
    Arduino Nano 33 BLE, or Arduino Nano 33 BLE Sense board.

  You can use a generic BLE central app, like LightBlue (iOS and Android) or
  nRF Connect (Android), to interact with the services and characteristics
  created in this sketch.

  This example code is in the public domain.
*/

#include <ArduinoBLE.h>

 // BLE Battery Service
BLEService batteryService("180F");

// BLE Battery Level Characteristic
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

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

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

    while (1);
  }

  /* Set a local name for the BLE device
     This name will appear in advertising packets
     and can be used by remote devices to identify this BLE device
     The name can be changed but maybe be truncated based on space left in advertisement packet
  */
  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

  /* Start advertising BLE.  It will start continuously transmitting BLE
     advertising packets and will be visible to remote BLE central devices
     until it receives a new connection */

  // start advertising
  BLE.advertise();

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

void loop() {
  // wait for a BLE central
  BLEDevice central = BLE.central();

  // if a central is connected to the peripheral:
  if (central) {
    Serial.print("Connected to central: ");
    // print the central's BT address:
    Serial.println(central.address());
    // turn on the LED to indicate the connection:
    digitalWrite(LED_BUILTIN, HIGH);

    // check the battery level every 200ms
    // while the central is connected:
    while (central.connected()) {
      long currentMillis = millis();
      // if 200ms have passed, check the battery level:
      if (currentMillis - previousMillis >= 200) {
        previousMillis = currentMillis;
        updateBatteryLevel();
      }
    }
    // when the central disconnects, turn off the LED:
    digitalWrite(LED_BUILTIN, LOW);
    Serial.print("Disconnected from central: ");
    Serial.println(central.address());
  }
}

void updateBatteryLevel() {
  /* Read the current voltage level on the A0 analog input pin.
     This is used here to simulate the charge level of a battery.
  */
  int battery = analogRead(A0);
  int batteryLevel = map(battery, 0, 1023, 0, 100);

  if (batteryLevel != oldBatteryLevel) {      // if the battery level has changed
    Serial.print("Battery Level % is now: "); // print it
    Serial.println(batteryLevel);
    batteryLevelChar.writeValue(batteryLevel);  // and update the battery level characteristic
    oldBatteryLevel = batteryLevel;           // save the level for next comparison
  }
}

May be worth using some of the code from the example "PeripheralExplorer".

Within this code there is this snippet which may help you see what characteristics are listed (as they may well be there, but in lower case):

/* place this within the main loop once connected */
  // loop the characteristics of the service and explore each
  for (int i = 0; i < service.characteristicCount(); i++) {
    BLECharacteristic characteristic = service.characteristic(i);

    exploreCharacteristic(characteristic);
  }

/* --------------------------------------------------------------------------- */
/* then separate function */
void exploreCharacteristic(BLECharacteristic characteristic) {
  // print the UUID and properties of the characteristic
  Serial.print("\tCharacteristic ");
  Serial.print(characteristic.uuid());
  Serial.print(", properties 0x");
  Serial.print(characteristic.properties(), HEX);

  // check if the characteristic is readable
  if (characteristic.canRead()) {
    // read the characteristic value
    characteristic.read();

    if (characteristic.valueLength() > 0) {
      // print out the value of the characteristic
      Serial.print(", value 0x");
      printData(characteristic.value(), characteristic.valueLength());
    }
  }
  Serial.println();

}

Here is an example for a BatteryService central that should get you started.

/*
  This example creates a BLE central that scans for a peripheral with a Battery Service.
  If that contains batteryLevel characteristics the value is displayed.

  The circuit:
  - Arduino Nano 33 BLE or Arduino Nano 33 IoT board.

  This example code is in the public domain.
*/

#include <ArduinoBLE.h>

//----------------------------------------------------------------------------------------------------------------------
// BLE UUIDs
//----------------------------------------------------------------------------------------------------------------------

// https://www.bluetooth.com/specifications/gatt/services//
// https://www.bluetooth.com/specifications/gatt/characteristics/

#define BLE_UUID_BATTERY_SERVICE                  "180F"
#define BLE_UUID_BATTERY_LEVEL                    "2A19"


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

  BLE.begin();

  BLE.scanForUuid( BLE_UUID_BATTERY_SERVICE );
}


void loop()
{
  static long previousMillis = 0;

  long interval = 5000;
  unsigned long currentMillis = millis();
  if ( currentMillis - previousMillis > interval )
  {
    previousMillis = currentMillis;

    BLEDevice peripheral = BLE.available();

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

      BLE.stopScan();

      explorePeripheral( peripheral );

      BLE.scanForUuid( BLE_UUID_BATTERY_SERVICE );
    }
  }
}


bool explorePeripheral( BLEDevice peripheral )
{
  if ( !peripheral.connect() )
  {
    return false;
  }

  if ( !peripheral.discoverAttributes() )
  {
    peripheral.disconnect();
    return false;
  }

  BLECharacteristic batteryLevelCharacteristic = peripheral.characteristic( BLE_UUID_BATTERY_LEVEL );
  if ( batteryLevelCharacteristic )
  {
    uint8_t batteryLevel;
    batteryLevelCharacteristic.readValue( batteryLevel );
    Serial.print( "Battery: " );
    Serial.print( batteryLevel );
    Serial.println( "%" );
  }

  peripheral.disconnect();
  return true;
}

Let me know if you have any questions.

@Klaus_K - thanks for that code. I ran it a few times (I set interval to 1000 ms). It makes it to if ( peripheral) and it doesn't find anything so it loops around again testing the milisec counter.

Once the millisec counter exceeds the threshold, it gets previousMillis = currentMillis but it hangs right after that i.e. it seems to be stuck at BLEDevice peripheral = BLE.available();

I'll need to troubleshoot this further but it seems to be a hard stop at that BLE.available() command.

I'll add, I can repeatably connect/disconnect/etc to the peripheral via my iPhone (using LightBlue) so I know the peripheral is doing what it is supposed to do.

It looks like the BLE module does not like when you tell it to scan and then you do not try to get the data fast enough and wait.

You can try to set the interval to 50ms as a quick fix.

Here is a different version where I start the scan only when the interval is over and then look for peripherals. The whole thing is only there because I thought a battery service is probably not something you want to scan continuously at high speed but every minute or so.

/*
  This example creates a BLE central that scans for a peripheral with a Battery Service.
  If that contains batteryLevel characteristics the value is displayed.

  The circuit:
  - Arduino Nano 33 BLE or Arduino Nano 33 IoT board.

  This example code is in the public domain.
*/

#include <ArduinoBLE.h>

//----------------------------------------------------------------------------------------------------------------------
// BLE UUIDs
//----------------------------------------------------------------------------------------------------------------------

// https://www.bluetooth.com/specifications/gatt/services//
// https://www.bluetooth.com/specifications/gatt/characteristics/

#define BLE_UUID_BATTERY_SERVICE                  "180F"
#define BLE_UUID_BATTERY_LEVEL                    "2A19"


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

  BLE.begin();
}


void loop()
{
  static long previousMillis = 0;

  long interval = 1000;
  unsigned long currentMillis = millis();
  // Be aware this statement will be executed many times every interval until a BatteryMonitor is found
  // because the previousMillis is updated only under a condition
  if ( currentMillis - previousMillis > interval )
  {
    BLE.scanForUuid( BLE_UUID_BATTERY_SERVICE );

    BLEDevice peripheral = BLE.available();

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

      previousMillis = currentMillis;

      BLE.stopScan();

      explorePeripheral( peripheral );
    }
  }
}


bool explorePeripheral( BLEDevice peripheral )
{
  if ( !peripheral.connect() )
  {
    return false;
  }

  if ( !peripheral.discoverAttributes() )
  {
    peripheral.disconnect();
    return false;
  }

  BLECharacteristic batteryLevelCharacteristic = peripheral.characteristic( BLE_UUID_BATTERY_LEVEL );
  if ( batteryLevelCharacteristic )
  {
    uint8_t batteryLevel;
    batteryLevelCharacteristic.readValue( batteryLevel );
    Serial.print( "Battery: " );
    Serial.print( batteryLevel );
    Serial.println( "%" );
  }

  peripheral.disconnect();
  return true;
}

There is a warning note in the code because I believe it uses a bad fix for now. It works but could lead beginners into a trap when they try to modify the code.

I will give that sketch a try tomorrow - thanks for sticking with me. In the meantime, while troubleshooting tonight, my MacBook Pro popped a message up saying that I should remove a USB device that is drawing too much power - I had my Nano 33 BLE and an HDMI/monitor connected to the USB-C port. I disconnected the HDMI and reran the sketch you provided earlier...and I gor more reliable Central Performance after running it ~4 times. So, I wonder if the odd behavior may have been due to a weak power signal - I will investigate further tomorrow before I try your new sketch.

Also, how often should the bool explorePeripheral( BLEDevice peripheral ) section of code run? I'm actually NEVER getting any indication that any part of that code is being run (i.e. I put Serial.println statements before the "return false" statements - I never see those and I never see Serial.print( "Battery: " ); Serial.print( batteryLevel ); Serial.println( "%" );

adr2018:
Also, how often should the bool explorePeripheral( BLEDevice peripheral ) section of code run?

You should get one new value every interval (e.g. 1 second) because the example scans for BatteryMonitor only.

adr2018:
I'm actually NEVER getting any indication that any part of that code is being run

You can add some Serial.Print statements inside the code to see it working. I usually remove these in examples to avoid character limitations in the forum.

The yellow LED of your Battery Monitor should blink every time a connection is successful. When it stays ON you need to reset the Battery Monitor.

The new sketch is working much better. Let me know how your tests work out.

You can try to set the interval to 50ms as a quick fix.

IMHO, adding in those interval timings is an over complication or over elaboration. Not needed. Add in a yield() instead if concerned.

I loaded and ran Klaus_K's second sketch and it does function better. I'm able to get the Central to connect to the Peripheral and I get reports of Battery Level on both sides. That't the good news. The bad news is that the Central continues to report Battery Level for anywhere between 1 and ~35 times and then it (the Central) locks up - the peripheral sits idle. If I press the hard reset on the Central Nano 33 BLE, the Central picks up and starts reporting again. I will poke around to see if there is a way to do a soft reset that can be initiated if/when the Central locks up (though it would be ideal to figure out what causes the Central to lock up in the first place).

Returning to your original post...

Ultimately, I want to get two Nano 33 BLEs to act as peripherals that send their respective battery levels to a third Nano 33 BLE acting as the central. My first step though is to just get one peripheral to send its battery level to the central.

What this does not say is whether you want your Central device to remain connected to the peripheral devices for an extended period or stay connected continuously or do you want the central device to scan - connect - read the battery value - disconnect - and then repeat the process.

The method chosen may help resolve a few things.

Yes, indeed, ultimately, I do want the Central to get updates from each (two or more) Peripheral. I wasn't sure if that could be done with a continuous Central-Peripheral(s) connection or, if the Central has to connect/disconnect/connect/disconnect from each Peripheral.

adr2018:
I loaded and ran Klaus_K's second sketch and it does function better.

Glad to hear that. :slight_smile:

adr2018:
(though it would be ideal to figure out what causes the Central to lock up in the first place).

I agree, I will do some further investigations. Maybe there is some queue filling up during the scan.
Do you have a lot of BLE devices nearby? I have about 4-5 additional ones I can see on my phones BLE scanner.

How often would you want to read your Battery Monitors?

adr2018:
Yes, indeed, ultimately, I do want the Central to get updates from each (two or more) Peripheral. I wasn't sure if that could be done with a continuous Central-Peripheral(s) connection or, if the Central has to connect/disconnect/connect/disconnect from each Peripheral.

It very much depends on what else you want to do in your application. For example, are you planning to transfer other data or are you planning to control the device via Bluetooth. If this is the case then connecting would probably be necessary.

If neither, then do you consider the battery data to be private, as in, you do not want other devices to be able to read this data. This determines whether you simply want to advertise the battery data or whether you want a device to connect before exchanging the data. Of course, using the advertising method requires a little more development work, so if we assume it is via connection, then the key determinant is how often you want to check the battery voltages. Unless the battery is critical I would assume maybe once a day or every couple of hours. In this case it does not warrant maintaining the connection.

So assuming this is your best approach, you may wish, for testing purposes, to place a long delay after the disconnect request as this should prevent the central device from freezing up.

I do have quite a few BLE devices around - LightBlue picks up ~12 which accounts for family computers, iPads, etc.

As for how often I want to to read the battery monitors, is it fair to say: whenever the battery level changes? To that, you would say: well, the battery level won't change THAT much over the course of a few hours so why check it very often? The rationale is that I was hoping to have a peripheral-central design pattern that would work for low frequency and high frequency (~1 sec) peripheral changes.

Maybe that is just too ambitious to expect out of an Arduino.

I had anticipated that the fact that the peripheral sets up the battery characteristic as BLERead | BLENotify that the peripheral would notify the central of a change in the peripheral's status at which time the Central would read the peripheral's characteristic value. The likelihood of two or more peripherals notifying the Central at exactly the same time seems like it would be small so the odds of a concurrent notification - while not zero - wouldn't be huge.

In any case, with the latest sketch, I can get at most ~25 and more often just about 10 values transferred to the Central before the only remedy seems to be a hard reset.

Again, maybe this is all too ambitious to expect a Nano 33 BLE to perform as a Central.

adr2018:
Maybe that is just too ambitious to expect out of an Arduino.

The Nano 33 BLE and IoT have enough power to handle this. The radio is in hardware and the processor can handle the data easily. BLE does not have a high data bandwidth. This is a low power protocol.

adr2018:
As for how often I want to to read the battery monitors, is it fair to say: whenever the battery level changes? ... The rationale is that I was hoping to have a peripheral-central design pattern that would work for low frequency and high frequency (~1 sec) peripheral changes.

In that case we can try whether we can connect to multiple peripherals at the same time.

adr2018:
I had anticipated that the fact that the peripheral sets up the battery characteristic as BLERead | BLENotify that the peripheral would notify the central of a change in the peripheral's status at which time the Central would read the peripheral's characteristic value.

The notification can only be used when you stay connected. Lets try this.

adr2018:
The likelihood of two or more peripherals notifying the Central at exactly the same time seems like it would be small so the odds of a concurrent notification - while not zero - wouldn't be huge.

This should not be an issue if we can figure out how to connect to multiple peripherals. The Bluetooth stack will take care of that. The notification is part of the class that handles the connection, so it will be separate for each peripheral.

adr2018:
In any case, with the latest sketch, I can get at most ~25 and more often just about 10 values transferred to the Central before the only remedy seems to be a hard reset.

That is OK. My initial thought was that you might only need to read the battery level every few minutes or longer. Then disconnecting and reconnecting would be the better solution to save energy.

adr2018:
Again, maybe this is all too ambitious to expect a Nano 33 BLE to perform as a Central.

There might be issues with the library or our sketch but the hardware itself is more than capable.

Alright, your words are encouraging - I'll keep at it.

Sticking with just one peripheral for a moment, I inserted quite a few Serial.println statements in the central code. The issue seems to arise at this statement: explorePeripheral( peripheral ) - the code cycle through that statement a number of times but it eventually pauses there and then if ( peripheral ) always remains 0 and then it never re-enters that if block.

Is there a way to reinitialize things such that if ( peripheral ) can pick up the peripheral again?

I've included the Central code here (along with some annoying Serial.println statements) followed by the peripheral code which is the unchanged BatteryMonitor example.

/*
  This example creates a BLE central that scans for a peripheral with a Battery Service.
  If that contains batteryLevel characteristics the value is displayed.

  The circuit:
  - Arduino Nano 33 BLE or Arduino Nano 33 IoT board.

  This example code is in the public domain.
*/

#include <ArduinoBLE.h>

//----------------------------------------------------------------------------------------------------------------------
// BLE UUIDs
//----------------------------------------------------------------------------------------------------------------------

// https://www.bluetooth.com/specifications/gatt/services//
// https://www.bluetooth.com/specifications/gatt/characteristics/

#define BLE_UUID_BATTERY_SERVICE                  "180F"
#define BLE_UUID_BATTERY_LEVEL                    "2A19"
int counter = 1;
int looper = 0;

void setup()
{
  Serial.begin( 9600 );
  while ( !Serial );
  pinMode(LED_BUILTIN, OUTPUT);
  BLE.begin();
}


void loop()
{
  static long previousMillis = 0;

  long interval = 1000;
  unsigned long currentMillis = millis();
  // Be aware this statement will be executed many times every interval until a BatteryMonitor is found
  // because the previousMillis is updated only under a condition
  /*  Serial.print("Millisecs");
      Serial.print(" ");
      Serial.print(currentMillis);
      Serial.print(" ");
      Serial.print(previousMillis);
      Serial.print(" ");
      Serial.println(currentMillis - previousMillis);
  */

  if ( currentMillis - previousMillis > interval )
  {
    //    Serial.println("  Inside timing loop");

    BLE.scanForUuid( BLE_UUID_BATTERY_SERVICE );
    //    Serial.println("Scanned for UUID");

    BLEDevice peripheral = BLE.available();
    //    Serial.println("Found BLE Available");

    if ( peripheral )
    {
      looper = 0;
      Serial.println("Initial peripheral connect");
      if ( peripheral.localName() != "BatteryMonitor" )
      {
        Serial.print("Local Name");
        Serial.println(peripheral.localName());
        return;
      }

      previousMillis = currentMillis;

      BLE.stopScan();
      Serial.println("Calling bool function");
      explorePeripheral( peripheral );
      Serial.println("Returned from bool function");

    }
    else {
      looper = looper + 1;
      Serial.print("Looper counter: ");
      Serial.println(looper);
      //      if (looper > 9) {
      //        looper = 0;
      //       BLE.stopScan();
    }
    //    }
  }
}


bool explorePeripheral( BLEDevice peripheral )
{
  if ( !peripheral.connect() )
  {
    Serial.println("Did not connect to peripheral");
    return false;
  }

  if ( !peripheral.discoverAttributes() )
  {
    peripheral.disconnect();
    Serial.println("Did not find Attributes and deconnected from peripheral");
    return false;
  }

  BLECharacteristic batteryLevelCharacteristic = peripheral.characteristic( BLE_UUID_BATTERY_LEVEL );
  if ( batteryLevelCharacteristic )
  {
    uint8_t batteryLevel;
    batteryLevelCharacteristic.readValue( batteryLevel );
    Serial.print( "Battery: " );
    Serial.print( batteryLevel );
    Serial.println( "%" );
    digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
    delay(1000);                       // wait for a second
    digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
    delay(1000);                       // wait for a second
  }
  Serial.print("Counter: ");
  Serial.println(counter);
  counter = counter + 1;
  peripheral.disconnect();
  Serial.println("Disconnect happened");
  return true;
}
/*
  Battery Monitor

  This example creates a BLE peripheral with the standard battery service and
  level characteristic. The A0 pin is used to calculate the battery level.

  The circuit:
  - Arduino MKR WiFi 1010, Arduino Uno WiFi Rev2 board, Arduino Nano 33 IoT,
    Arduino Nano 33 BLE, or Arduino Nano 33 BLE Sense board.

  You can use a generic BLE central app, like LightBlue (iOS and Android) or
  nRF Connect (Android), to interact with the services and characteristics
  created in this sketch.

  This example code is in the public domain.
*/

#include <ArduinoBLE.h>

 // BLE Battery Service
BLEService batteryService("180F");

// BLE Battery Level Characteristic
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

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

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

    while (1);
  }

  /* Set a local name for the BLE device
     This name will appear in advertising packets
     and can be used by remote devices to identify this BLE device
     The name can be changed but maybe be truncated based on space left in advertisement packet
  */
  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

  /* Start advertising BLE.  It will start continuously transmitting BLE
     advertising packets and will be visible to remote BLE central devices
     until it receives a new connection */

  // start advertising
  BLE.advertise();

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

void loop() {
  // wait for a BLE central
  BLEDevice central = BLE.central();

  // if a central is connected to the peripheral:
  if (central) {
    Serial.print("Connected to central: ");
    // print the central's BT address:
    Serial.println(central.address());
    // turn on the LED to indicate the connection:
    digitalWrite(LED_BUILTIN, HIGH);

    // check the battery level every 200ms
    // while the central is connected:
    while (central.connected()) {
      long currentMillis = millis();
      // if 200ms have passed, check the battery level:
      if (currentMillis - previousMillis >= 200) {
        previousMillis = currentMillis;
        updateBatteryLevel();
      }
    }
    // when the central disconnects, turn off the LED:
    digitalWrite(LED_BUILTIN, LOW);
    Serial.print("Disconnected from central: ");
    Serial.println(central.address());
  }
}

void updateBatteryLevel() {
  /* Read the current voltage level on the A0 analog input pin.
     This is used here to simulate the charge level of a battery.
  */
  int battery = analogRead(A0);
  int batteryLevel = map(battery, 0, 1023, 0, 100);

  if (batteryLevel != oldBatteryLevel) {      // if the battery level has changed
    Serial.print("Battery Level % is now: "); // print it
    Serial.println(batteryLevel);
    batteryLevelChar.writeValue(batteryLevel);  // and update the battery level characteristic
    oldBatteryLevel = batteryLevel;           // save the level for next comparison
  }
}

If anyone is interested in continuing to explore the issues I've been running into, I have a bit more troubleshooting to report. Specifically, I am using the BatterMonitor peripheral example unchanged. For the Central, I switched over to trying to modify the PeripheralExplorer sketch - which I've included here. Overall, this seem to operate more reliably than the previous Central sketches I have tried, however, it does run into the same problem I noted in the earlier posts. Specifically, every once in a while, the sketch gets to lines 83 - 98 and it hangs up. You can see from the modifications I inserted in that section of code, I tried to reinitialize the BLE connection, however, that does not seem to have the desired effect.

I am not quite sure why the Attribute failure leads to not being able to re-establish a connection to a peripheral (when the Central doesn't find the BatteryMonitor peripheral, the Central doesn't find ANY peripheral thereafter unless I do a hard reset), however, is there a way to work around the failure to re-establish a peripheral search/connections?

/*
  Peripheral Explorer

  This example scans for BLE peripherals until one with a particular name ("LED")
  is found. Then connects, and discovers + prints all the peripheral's attributes.

  The circuit:
  - Arduino MKR WiFi 1010, Arduino Uno WiFi Rev2 board, Arduino Nano 33 IoT,
    Arduino Nano 33 BLE, or Arduino Nano 33 BLE Sense board.

  You can use it with another board that is compatible with this library and the
  Peripherals -> LED example.

  This example code is in the public domain.
*/

#include <ArduinoBLE.h>

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

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

    while (1);
  }

  Serial.println("BLE Central - Peripheral Explorer");

  // start scanning for peripherals
  //  BLE.scan();
}

int counter = 0;

void loop() {
  // check if a peripheral has been discovered
  counter = counter + 1;
  BLE.scan();
  BLEDevice peripheral = BLE.available();
  Serial.println(" ");
  Serial.print("*********************** ");
  Serial.println(counter);
  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();

    // see if peripheral is a LED
    if (peripheral.localName() == "BatteryMonitor") {
      // stop scanning
      BLE.stopScan();

      Serial.println("Heading into explorerPeripheral function");
      explorerPeripheral(peripheral);
      Serial.println("Returned from explorerPeripheral function");
      // peripheral disconnected, we are done
      //      while (1) {
      // do nothing
      //      }
    }
  }
}

void explorerPeripheral(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(); // just prior to this line, the sketch seems to freeze.
    // the following lines are meant to try to restart the BLE but it apparently does not have the desired
    // effect - when the sketch returns to the loop, it does not re-establish a connection to any peripheral
    BLE.end();
    if (!BLE.begin()) {
      Serial.println("Restarting BLE failed!");
    }

    return;
  }

  // read and print device name of peripheral
  Serial.println();
  Serial.print("Device name: ");
  Serial.println(peripheral.deviceName());
  Serial.print("Appearance: 0x");
  Serial.println(peripheral.appearance(), HEX);
  Serial.println();

  // loop the services of the peripheral and explore each
  for (int i = 0; i < peripheral.serviceCount(); i++) {
    BLEService service = peripheral.service(i);

    exploreService(service);
  }

  Serial.println();

  // we are done exploring, disconnect
  Serial.println("Disconnecting ...");
  peripheral.disconnect();
  Serial.println("Disconnected");
}

void exploreService(BLEService service) {
  // print the UUID of the service
  Serial.print("Service ");
  Serial.println(service.uuid());

  // loop the characteristics of the service and explore each
  for (int i = 0; i < service.characteristicCount(); i++) {
    BLECharacteristic characteristic = service.characteristic(i);

    exploreCharacteristic(characteristic);
  }
}

void exploreCharacteristic(BLECharacteristic characteristic) {
  // print the UUID and properties of the characteristic
  Serial.print("\tCharacteristic ");
  Serial.print(characteristic.uuid());
  Serial.print(", properties 0x");
  Serial.print(characteristic.properties(), HEX);

  // check if the characteristic is readable
  if (characteristic.canRead()) {
    // read the characteristic value
    characteristic.read();

    if (characteristic.valueLength() > 0) {
      // print out the value of the characteristic
      Serial.print(", value 0x");
      printData(characteristic.value(), characteristic.valueLength());
    }
  }
  Serial.println();

  // loop the descriptors of the characteristic and explore each
  for (int i = 0; i < characteristic.descriptorCount(); i++) {
    BLEDescriptor descriptor = characteristic.descriptor(i);

    exploreDescriptor(descriptor);
  }
}

void exploreDescriptor(BLEDescriptor descriptor) {
  // print the UUID of the descriptor
  Serial.print("\t\tDescriptor ");
  Serial.print(descriptor.uuid());

  // read the descriptor value
  descriptor.read();

  // print out the value of the descriptor
  Serial.print(", value 0x");
  printData(descriptor.value(), descriptor.valueLength());

  Serial.println();
}

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);
  }
}

Wow, sorry to hear that you are still having problems.

I have no idea why you are having problems with the discoverAttributes command.

But let me start unpacking as cleaner simpler code can make troubleshooting less painful...

I am using the BatterMonitor peripheral example unchanged.

In my opinion, there is a small flaw in the logic of this example.

I realise this is a simulated example, but there is no need to check battery voltage every 200ms. Makes no sense at all. As such, I would suggest maybe applying a check every 2 seconds for testing or even longer. This at least reduces BLE wireless "traffic".

    while (central.connected()) {
      long currentMillis = millis();
      // if 2000ms have passed (suggest even using 10 seconds here), check the battery level: 
      if (currentMillis - previousMillis >= 2000) {
        previousMillis = currentMillis;
        updateBatteryLevel();
      }
    }

IMHO, that should make it a little easier to test things (as you can visually observe updates etc.) when your central device is connected to the peripheral device.

For the Central, I switched over to trying to modify the PeripheralExplorer sketch - which I've included here.

This central sketch includes a ton of stuff you do not really need.

Besides that, the key thing to starting thinking about is how your central device is scanning for your peripheral devices.

The PeripheralExplorer sketch is merely doing a generic scan using BLE.scan();

That's fine if you do not have any other Bluetooth devices about, but if you do then this will pick up everything.

An alternative and somewhat cleaner method is found in the LedControl example, where this sketch uses BLE.scanForUuid("19b10000-e8f2-537e-4f6c-d104768a1214");

This is more targeted as it is filtering the scan list to match just that UUID.

If you look back at the BatteryMonitor sketch, it adds this service: BLE.setAdvertisedService(batteryService); // add the service UUID

As such you can then change that scanForUuid command to match this battery service UUID, which is "180F".

So this would read BLE.scanForUuid("180F"); and this should pick up all those peripherals just advertising that service.

I see you are then making a further check to see if the device name matches. That's fine.

Now moving on to explorerPeripheral(peripheral);

This is where you have too much unnecessary code (in my opinion) if all you want is the battery voltages.

I'll strip it down for you...

Firstly, there is nothing wrong with this logic. If it fails the check it returns you to the main loop. So my suggestion is leave as is... the code in the main loop could then change to suit... look at that later.

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

Then as per LedController example, define your Batter Level characteristic below the attributes check, using...

BLECharacteristic batteryLevelChar = peripheral.characteristic("2A19");

and create the following logic to check that that characteristic can be found:

  BLECharacteristic batteryLevelChar = peripheral.characteristic("2A19");
  if (!batteryLevelChar ) {
    Serial.println("Peripheral does not have Battery Level characteristic!");
    peripheral.disconnect();
    return;
  }

Then you'll need to check if the peripheral can notify your central device when new data arrives.

This typically requires setting up a callback function.... Check the ScanCallback example for clues as there is not much else to go on.

Alternatively you can read the data too.

Here you can use the LedControl example but use canRead instead of canWrite check. etc.

Anyhow, keep plugging away... I'm just unable to be of more assistance for now. Sorry about that.