BLE very weak signal

Its the components on the green antenna area.

Klaus_K:

/*

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

#define BLE_UUID_TEST_SERVICE               “9A48ECBA-2E92-082F-C079-9E75AAE428B1”
#define BLE_UUID_ACCELERATION               “2713”
#define BLE_UUID_COUNTER                    “1A3AC130-31EE-758A-BC50-54A61958EF81”
#define BLE_UUID_RESET_COUNTER              “FE4E19FF-B132-0099-5E94-3FFB2CF07940”

Hi Klaus
I am fairly new to the Arduino platform. I am using Arduino nano 33 BLE sense for my project. Could you please help me that how you know what UUID represents what? I want to access all 9 axis IMU values using UUID. I can not use the library since I am working on Matlab. Therefore I need to work with reading values using UUID.

Thanks in advance. I would be really grateful if you could help.

I have also attached the Arduino BLE object characteristics that I can access from my Matlab code.

rakhiag:
Could you please help me that how you know what UUID represents what?

As the developer of your Arduino Nano 33 BLE project, you must decide what data your Arduino is providing via the Bluetooth GATT (that is the structure defined in the specification to hold the data).
Each service or characteristic uses a UUID.
The Bluetooth org has specified a list of 16-bit UUID for things that the member of the org wanted to produce e.g. heart rate sensors for sport watches, temperature sensor for air condition and heating system ... and so on. If your device can use these UUIDs your device could automatically be compatible with other things that know these UUIDs.

Because the Bluetooth org has members that will develop stuff that will be unique and they have customers like us that will create even more unique stuff they have specified that you can create your own UUIDs but they have to be random 128 bit numbers. This way it is unlikely two devices specify the same UUID for different things.

So, the UUID for acceleration I looked up on the Bluetooth org site and the other UUID I created with a random number macro for the example. I could have created a random UUID for the acceleration as well, because my demo does not need to be compatible with some application. I just wanted to test both UUID formats.

I created the #define names so my source code is not littered with numbers and I can change them easier. You can also place them in a header file and compile them into two different Bluetooth nodes e.g. server and client.

Hi @Klaus_K

Understood. My application just worked. Thanks a lot! Can you help with just 1 more doubt? The value of x- accelerometer that I receive is 1*4 array. Do you know how I can interpret this? Is it some byte value that needs to be converted to an integer?

Also, if I can write all 3 values (X, Y, Z) using single writevalue command in the same characteristic?

rakhiag:
Also, if I can write all 3 values (X, Y, Z) using single writevalue command in the same characteristic?

You can create a fixed length string characteristic and combine your values into a comma separated "string". Have a look at the example I created in the following post replay #1

BUT, the nice thing about the individual characteristics is the clean easy interface. Here is why I think its the better solution

  • no need to convert the value to chars on the Arduino
  • no need to convert the chars back to the value on the client
  • no need to know the protocol of your string e.g. X,Y,Z or Z,Y,X
  • the client can pick only one value if it does not need all three

If you must use a single characteristic because of some limitations on the client, add it as an additional option. Maybe later you figure out a way to do it the right way on the client. Then you do not need to reprogram the Arduino.

Regarding the x-accelerometer, could you provide some more information?

Hi @Klaus_K

Could you help me with 1 more query regarding the same topic? Now that I have setup the device id, characterstic etc for Arduino BLE device, how can I read this data in c#? Basically, I don't understand how can read data over BLE in c#

I am not really into PC programming. I did a short Google search and it looks like there is good documentation available from Microsoft. Start here

There also seem to be some Youtube videos available. Some are an hour or so, which promises some more details but will require some time to watch and figure out how good the content really is.

Other than the fact that the library functions will look different I would expect you find similar features to the Arduino BLE library e.g. UUIDs, characteristics and services …

What tools are you using for C#? Are these paid licenses or is there a free “hobby” version available?

Thanks a lot for your response @Klaus_K

I tried different methods and finally found that using Bluetooth with Unity is not straightforward. I am considering to use a plugin. But I require to use my Arduino nano 33 BLE sense as Bluetooth (not BLE) for that plugin. On the product page, it shows that this Arduino can be used as both BLE and classic Bluetooth. However, I am only able to use it as BLE.

Do you know about this? How can I use my Arduino BLE as classic Bluetooth?

RakhiAg:
But I require to use my Arduino nano 33 BLE sense as Bluetooth (not BLE) for that plugin. On the product page, it shows that this Arduino can be used as both BLE and classic Bluetooth. However, I am only able to use it as BLE.

Do you know about this? How can I use my Arduino BLE as classic Bluetooth?

This is badly worded on the Arduino web page. You can use the Nano 33 BLE as BLE client and BLE server but not Bluetooth Classic. Bluetooth Classic requires a different radio that is more expensive to build in silicon. That is why there are BLE only chips. They can be built cheaper. This was a designed into the BLE standard because it was hoped this would increase the chance of these chips going everywhere.

If you check the ublox web page and the NINA data sheets you can see, they clearly mark devices that can do both as dual mode.
The NINA-306 (the module on the Nano 33 BLE) clearly states Bluetooth 5 low energy all over the document.
While the NINA-W102 (the module on the Nano 33 IoT) states Bluetooth 4.2, BLE and WiFi.

There is a post about the Nano 33 IoT being converted to Bluetooth Classic. This is not supported by ArduinoBLE library, but the replies seem quite positive about the process described. I have not tried it myself.

Klaus_K:
Sure. Here you go. You will need to reset the board once the connection was lost completely. I haven’t found a way to recover the BLE stack from that.

/*

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

#define BLE_UUID_TEST_SERVICE              "9A48ECBA-2E92-082F-C079-9E75AAE428B1"
#define BLE_UUID_ACCELERATION              "2713"
#define BLE_UUID_COUNTER                    "1A3AC130-31EE-758A-BC50-54A61958EF81"
#define BLE_UUID_RESET_COUNTER              "FE4E19FF-B132-0099-5E94-3FFB2CF07940"

//----------------------------------------------------------------------------------------------------------------------
// BLE
//----------------------------------------------------------------------------------------------------------------------

Hi Klaus_K

Thanks a lot for all your previous responses. I want to ask if there's a reason for defining the characteristics in this manner? Can I define it in some simple alphanumeric digits? For example instead of "9A48ECBA-2E92-082F-C079-9E75AAE428B1", can I just use "UUID-TEST-123"?

RakhiAg:
I want to ask if there's a reason for defining the characteristics in this manner? Can I define it in some simple alphanumeric digits? For example instead of "9A48ECBA-2E92-082F-C079-9E75AAE428B1", can I just use "UUID-TEST-123"?

The BLE specification requires all non-standardized characteristics and services to use random 128-bit UUID's. This will make it very unlikely two engineers will pick the same UUID. Anyone can just create their own UUID without any organization managing the distribution. There is enough UUID space for years to come.
The Bluetooth SIG has created 16-bit UUID's for common applications e.g. sensors for sport, medical application and many more. Some BLE apps for phones decode these, which is useful during development.

Note: The UUIDs in the library are used in String format but they are hexadecimal numbers. So, "UUID-TEST-123" would not work and it is not random.

Thanks a lot, @Klaus_K for all your answers. Can I bother you with just one more doubt? I can clearly read the values of the built-in sensors of the board using the code you posted on this thread. But how do I read the values from an external sensor attached to the board via i2c? How will this code be modified in that case? I have attached an external IMU at A4 and A5 (SDA and SCL pins). But how do I access those pins?

Edit: I think this is not related to this thread, so I started a new topic for this question: How to read external I2C from Arduino Nano 33 BLE board? - Nano 33 BLE - Arduino Forum

Klaus_K:

#include <ArduinoBLE.h>

#include <Arduino_LSM9DS1.h>

#define BLE_UUID_TEST_SERVICE               “9A48ECBA-2E92-082F-C079-9E75AAE428B1”
#define BLE_UUID_ACCELERATION               “2713”
#define BLE_UUID_COUNTER                    “1A3AC130-31EE-758A-BC50-54A61958EF81”
#define BLE_UUID_RESET_COUNTER              “FE4E19FF-B132-0099-5E94-3FFB2CF07940”

BLEService testService( BLE_UUID_TEST_SERVICE );
BLEFloatCharacteristic accelerationCharacteristic( BLE_UUID_ACCELERATION, BLERead | BLENotify );
BLEUnsignedLongCharacteristic counterCharacteristic( BLE_UUID_COUNTER, BLERead | BLENotify );
BLEBoolCharacteristic resetCounterCharacteristic( BLE_UUID_RESET_COUNTER, BLEWriteWithoutResponse );

const int BLE_LED_PIN = LED_BUILTIN;
const int RSSI_LED_PIN = LED_PWR;

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

pinMode( BLE_LED_PIN, OUTPUT );
 pinMode( RSSI_LED_PIN, OUTPUT );

if ( !IMU.begin() )
 {
   Serial.println( “Failed to initialize IMU!” );
   while ( 1 );
 }

if( setupBleMode() )
 {
   digitalWrite( BLE_LED_PIN, HIGH );
 }
}

void loop()
{
 static unsigned long counter = 0;
 static long previousMillis = 0;

BLEDevice central = BLE.central();

if ( central )
 {
   Serial.print( "Connected to central: " );
   Serial.println( central.address() );

while ( central.connected() )
   {
     if( resetCounterCharacteristic.written() )
     {
       counter = 0;
     }

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

Serial.print( "Central RSSI: " );
       Serial.println( central.rssi() );

if( central.rssi() != 0 )
       {
         digitalWrite( RSSI_LED_PIN, LOW );
         float accelerationX, accelerationY, accelerationZ;
         if ( IMU.accelerationAvailable() )
         {
           IMU.readAcceleration( accelerationX, accelerationY, accelerationZ );
           accelerationCharacteristic.writeValue( accelerationX );
         }

counter++;
         counterCharacteristic.writeValue( counter );
       }
       else
       {
         digitalWrite( RSSI_LED_PIN, HIGH );
       }
     }
   }
 }
}

bool setupBleMode()
{
 if ( !BLE.begin() )
 {
   return false;
 }

BLE.setDeviceName( “Arduino Nano 33 BLE” );
 BLE.setLocalName( “Arduino Nano 33 BLE” );
 BLE.setAdvertisedService( testService );

testService.addCharacteristic( accelerationCharacteristic );
 testService.addCharacteristic( counterCharacteristic );
 testService.addCharacteristic( resetCounterCharacteristic );

BLE.addService( testService );

accelerationCharacteristic.writeValue( 0.0 );
 counterCharacteristic.writeValue( 0 );

BLE.advertise();

return true;
}

Hi Klaus_K

In the above code that you provided earlier, accelerationX value is being written to a characteristic which we can read from our central device. However, the frequency at which I can call the data from the central device is limited in my case.

Is there a way that Arduino keeps a buffer and keeps recording, let’s say 20 consecutive samples, which I can later read from my central device? For example, if I can set read mode as ‘latest’ and read the last 20 samples from characteristic?

How should the above code be modified for that?

RakhiAg:
Is there a way that Arduino keeps a buffer and keeps recording, let's say 20 consecutive samples, which I can later read from my central device? For example, if I can set read mode as 'latest' and read the last 20 samples from characteristic?

Storing the values in the Arduino is no issue. You could use a circular buffer to store the values. It is easy to implement.

The more difficult issue is, how you read the data from the characteristic.

RakhiAg:
However, the frequency at which I can call the data from the central device is limited in my case.

If you cannot keep up with reading data, how can you catch up with the last 20 values while new values are created? You need to find a way to manage the data. This depends a bit on what your application needs.

There are a few things that can help.

Combine multiple values in one characteristic. This reduces the overhead for the read. There is a limit of how many bytes one transfer can send.

Reduce the datatype. This would allow to combine more values into the read operation. e.g. you can send two 16-bit values instead of one 32-bit.

If your sensor data is created slow enough for your central device to catch up, you could read the data in a "burst" style. e.g. read multiple values - delay - read multiple values ...

You can also think about data pre-processing and sending values where you can lose values for your application without any issues. For example, the BLE standard for bicycle sensor uses this technique. Instead of sending the current revolutions per time interval it sends the absolute number of revolutions every time. So, instead of 10-11-10 it sends 10-21-31.

You can combine these.

Klaus_K:
Combine multiple values in one characteristic. This reduces the overhead for the read. There is a limit of how many bytes one transfer can send.

Reduce the datatype. This would allow to combine more values into the read operation. e.g. you can send two 16-bit values instead of one 32-bit.

Thanks a lot for your answer Klaus_K. I have very little knowledge about all of this, so I understood parts of what you said. Kindly bear with me.

Can you tell me how do I combine multiple values in 1 characteristic? Basically, I am sending 4 quaternion values as 4 characteristics. Is there a way to send an array as a single characteristic perhaps?

How to reduce the datatype?

My end goal is to read quaternion values from the central device. Possibly all 4 quaternion values in single characteristic. In your code, you have defined characteristics as 'BLEFloatCharacteristic'. I am using the same format and defining 4 characteristics for 4 quaternion values. Because of this, it's taking too long to read from the central.

Your help is much appreciated. Thanks again.

RakhiAg:
Can you tell me how do I combine multiple values in 1 characteristic? Basically, I am sending 4 quaternion values as 4 characteristics. Is there a way to send an array as a single characteristic perhaps?

Here is an example for the peripheral

/*
  This example creates a BLE peripheral with a service containing a characeristic with multiple values combined.
  The yellow LED shows the BLE module is initialized.
  The green LED shows RSSI of zero. The more it blinks the worse the connection.

  The circuit:
  - 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 UUIDs
//----------------------------------------------------------------------------------------------------------------------

#define BLE_UUID_SENSOR_DATA_SERVICE              "2BEEF31A-B10D-271C-C9EA-35D865C1F48A"
#define BLE_UUID_MULTI_SENSOR_DATA                "4664E7A1-5A13-BFFF-4636-7D0A4B16496C"

#define NUMBER_OF_SENSORS 4

union multi_sensor_data
{
  struct __attribute__( ( packed ) )
  {
    float values[NUMBER_OF_SENSORS];
  };
  uint8_t bytes[ NUMBER_OF_SENSORS * sizeof( float ) ];
};

union multi_sensor_data multiSensorData;


//----------------------------------------------------------------------------------------------------------------------
// BLE
//----------------------------------------------------------------------------------------------------------------------

BLEService sensorDataService( BLE_UUID_SENSOR_DATA_SERVICE );
BLECharacteristic multiSensorDataCharacteristic( BLE_UUID_MULTI_SENSOR_DATA, BLERead | BLENotify, sizeof multiSensorData.bytes );


const int BLE_LED_PIN = LED_BUILTIN;
const int RSSI_LED_PIN = LED_PWR;


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

  pinMode( BLE_LED_PIN, OUTPUT );
  pinMode( RSSI_LED_PIN, OUTPUT );

  if ( setupBleMode() )
  {
    digitalWrite( BLE_LED_PIN, HIGH );
  }

  for ( int i = 0; i < NUMBER_OF_SENSORS; i++ )
  {
    multiSensorData.values[i] = i;
  }
}


void loop()
{
  #define UPDATE_INTERVALL 50
  static long previousMillis = 0;

  // listen for BLE peripherals to connect:
  BLEDevice central = BLE.central();

  if ( central )
  {
    Serial.print( "Connected to central: " );
    Serial.println( central.address() );

    while ( central.connected() )
    {
      unsigned long currentMillis = millis();
      if ( currentMillis - previousMillis > UPDATE_INTERVALL )
      {
        previousMillis = currentMillis;

        Serial.print( "Central RSSI: " );
        Serial.println( central.rssi() );

        if ( central.rssi() != 0 )
        {
          digitalWrite( RSSI_LED_PIN, LOW );

          for ( int i = 0; i < NUMBER_OF_SENSORS; i++ )
          {
            multiSensorData.values[i] = multiSensorData.values[i] + 0.1;
          }

          multiSensorDataCharacteristic.writeValue( multiSensorData.bytes, sizeof multiSensorData.bytes );
        }
        else
        {
          digitalWrite( RSSI_LED_PIN, HIGH );
        }
      }
    }

    Serial.print( F( "Disconnected from central: " ) );
    Serial.println( central.address() );
  }
}



bool setupBleMode()
{
  if ( !BLE.begin() )
  {
    return false;
  }

  // set advertised local name and service UUID:
  BLE.setDeviceName( "Arduino Nano 33 BLE" );
  BLE.setLocalName( "Arduino Nano 33 BLE" );
  BLE.setAdvertisedService( sensorDataService );

  // BLE add characteristics
  sensorDataService.addCharacteristic( multiSensorDataCharacteristic );

  // add service
  BLE.addService( sensorDataService );

  // set the initial value for the characeristic:
  multiSensorDataCharacteristic.writeValue( multiSensorData.bytes, sizeof multiSensorData.bytes );

  // start advertising
  BLE.advertise();

  return true;
}

Here is an example for the central

/*
  This example creates a BLE central that scans for a peripheral with a service containing a multi value characteristic.

  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
//----------------------------------------------------------------------------------------------------------------------

#define BLE_UUID_SENSOR_DATA_SERVICE              "2BEEF31A-B10D-271C-C9EA-35D865C1F48A"
#define BLE_UUID_MULTI_SENSOR_DATA                "4664E7A1-5A13-BFFF-4636-7D0A4B16496C"

#define NUMBER_OF_SENSORS 4

union multi_sensor_data
{
  struct __attribute__( ( packed ) )
  {
    float values[NUMBER_OF_SENSORS];
  };
  uint8_t bytes[ NUMBER_OF_SENSORS * sizeof( float ) ];
};

union multi_sensor_data multiSensorData;


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

  BLE.begin();

  BLE.scanForUuid( BLE_UUID_SENSOR_DATA_SERVICE );
}


void loop()
{
#define UPDATE_INTERVALL 10
  static long previousMillis = 0;

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

    BLEDevice peripheral = BLE.available();

    if ( peripheral )
    {
      if ( peripheral.localName() != "Arduino Nano 33 BLE" )
      {
        return;
      }

      BLE.stopScan();

      explorePeripheral( peripheral );

      BLE.scanForUuid( BLE_UUID_SENSOR_DATA_SERVICE );
    }
  }
}


bool explorePeripheral( BLEDevice peripheral )
{
  if ( !peripheral.connect() )
  {
    return false;
  }
  Serial.println( "BLE connected" );

  if ( !peripheral.discoverAttributes() )
  {
    peripheral.disconnect();
    return false;
  }
  Serial.println( "BLE attributes discovered" );

  BLECharacteristic multiSensorDataCharacteristic = peripheral.characteristic( BLE_UUID_MULTI_SENSOR_DATA );
  if ( !multiSensorDataCharacteristic )
  {
    peripheral.disconnect();
    return false;
  }
  Serial.println( "BLE characteristic found" );

  if ( !multiSensorDataCharacteristic.canSubscribe() )
  {
    peripheral.disconnect();
    return false;
  }
  Serial.println( "BLE characteristic can subscribe" );

  if ( !multiSensorDataCharacteristic.subscribe() )
  {
    peripheral.disconnect();
    return false;
  }
  Serial.println( "BLE characteristic subscribed" );

  while ( 1 ) // need to add logic to leave
  {
#define BLE_POLL_INTERVALL 5
    static long previousMillis = 0;
    unsigned long currentMillis = millis();
    if ( currentMillis - previousMillis > BLE_POLL_INTERVALL )
    {
      BLE.poll();

      if ( multiSensorDataCharacteristic.valueUpdated() )
      {
        Serial.println( "BLE new data" );

        multiSensorDataCharacteristic.readValue( multiSensorData.bytes, sizeof multiSensorData.bytes );
        for ( int i = 0; i < NUMBER_OF_SENSORS; i++ )
        {
          Serial.print( "Sensor (" );
          Serial.print( i );
          Serial.print( "): " );
          Serial.println( multiSensorData.values[i] );
        }
      }
    }
  }

  peripheral.disconnect();
  return true;
}

RakhiAg:
How to reduce the datatype?

That is up to you and depends on your data. For instance you can convert a float (32-bit) into a 16-bit integer with a fixed divider. e.g. 1.2513 x 100 → 125 => BLE => 125 / 100 = 1.25. You will loose some precision and range but you can send more data values.

It worked!!! You were right, speed increased drastically when I combined the values in single characteristic instead of reading multiple characteristics. Thanks a lot!

Hi @Klaus_K

Thanks a lot again for the code you posted earlier. It solved a lot of my problems. I have 1 more doubt. Do you know how can I now modify the code to write a multi-dimensional array as characteristic?

Since the rate at which I can read data from Arduino is slow, I want to write 5 consecutive sensor data points at a time to a single characteristic.

I made these modifications to the code you sent earlier:

#include <ArduinoBLE.h>

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

#define BLE_UUID_SENSOR_DATA_SERVICE              "2BEEF31A-B10D-271C-C9EA-35D865C1F48A"
#define BLE_UUID_MULTI_SENSOR_DATA                "4664E7A1-5A13-BFFF-4636-7D0A4B16496C"

#define NUMBER_OF_SENSORS 4
#define SAMPLES 5

union multi_sensor_data
{
  struct __attribute__( ( packed ) )
  {
    float values[SAMPLES][NUMBER_OF_SENSORS];
  };
  uint8_t bytes[SAMPLES][ NUMBER_OF_SENSORS * sizeof( float ) ];
};

union multi_sensor_data multiSensorData;


//----------------------------------------------------------------------------------------------------------------------
// BLE
//----------------------------------------------------------------------------------------------------------------------

BLEService sensorDataService( BLE_UUID_SENSOR_DATA_SERVICE );
BLECharacteristic multiSensorDataCharacteristic( BLE_UUID_MULTI_SENSOR_DATA, BLERead | BLENotify, sizeof multiSensorData.bytes );


const int BLE_LED_PIN = LED_BUILTIN;
const int RSSI_LED_PIN = LED_PWR;


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

  pinMode( BLE_LED_PIN, OUTPUT );
  pinMode( RSSI_LED_PIN, OUTPUT );

  if ( setupBleMode() )
  {
    digitalWrite( BLE_LED_PIN, HIGH );
  }

  for ( int j = 0; j < SAMPLES; j++ )
    for ( int i = 0; i < NUMBER_OF_SENSORS; i++ )
    {
      multiSensorData.values[j][i] = i;
    }
}


void loop()
{
#define UPDATE_INTERVALL 50
  static long previousMillis = 0;

  // listen for BLE peripherals to connect:
  BLEDevice central = BLE.central();

  if ( central )
  {
    Serial.print( "Connected to central: " );
    Serial.println( central.address() );

    while ( central.connected() )
    {
      unsigned long currentMillis = millis();
      if ( currentMillis - previousMillis > UPDATE_INTERVALL )
      {
        previousMillis = currentMillis;

        Serial.print( "Central RSSI: " );
        Serial.println( central.rssi() );

        if ( central.rssi() != 0 )
        {
          digitalWrite( RSSI_LED_PIN, LOW );

          for ( int j = 0; j < SAMPLES; j++ )
            for ( int i = 0; i < NUMBER_OF_SENSORS; i++ )
            {
              multiSensorData.values[j][i] = multiSensorData.values[j][i] + 0.1;
            }

          multiSensorDataCharacteristic.writeValue( multiSensorData.bytes, sizeof multiSensorData.bytes );
        }
        else
        {
          digitalWrite( RSSI_LED_PIN, HIGH );
        }
      }
    }

    Serial.print( F( "Disconnected from central: " ) );
    Serial.println( central.address() );
  }
}



bool setupBleMode()
{
  if ( !BLE.begin() )
  {
    return false;
  }

  // set advertised local name and service UUID:
  BLE.setDeviceName( "Arduino Nano 33 BLE" );
  BLE.setLocalName( "Arduino Nano 33 BLE" );
  BLE.setAdvertisedService( sensorDataService );

  // BLE add characteristics
  sensorDataService.addCharacteristic( multiSensorDataCharacteristic );

  // add service
  BLE.addService( sensorDataService );

  // set the initial value for the characeristic:
  multiSensorDataCharacteristic.writeValue( multiSensorData.bytes, sizeof multiSensorData.bytes );

  // start advertising
  BLE.advertise();

  return true;
}

The code compiles, but when I try to read the characteristic, I get the error that unable to read.

Two things:

The union is a C/C++ construct that overlays the same memory with different types and names. I use this because the BLE characteristic only cares about bytes but in the sketch we want to use different types e.g. float. So, you would need to make bytes a one dimensional array.

Second, the number of bytes in a BLE data packet is limited to around 32 bytes. There are some conditions in the BLE specification which I am not sure about. Just try how big you can get the structure.