What is the most efficient way to send accelerometer data over BLE to a Raspberry Pi?

Hi,

I am using Arduino Nano 33 BLE Sense, and sending data from Arduino to Raspberry Pi.

Currently, I created characteristic per parameter in accelerometer data as you can see below:

int16_t accelerometer_X = round(accSensorData.accX * 100.0);
accelerometerCharacteristic_X.writeValue(accelerometer_X);
    
int16_t accelerometer_Y = round(accSensorData.accY * 100.0);
accelerometerCharacteristic_Y.writeValue(accelerometer_Y);

int16_t accelerometer_Z = round(accSensorData.accZ * 100.0);
accelerometerCharacteristic_Z.writeValue(accelerometer_Z);

However, when I read from Pi, I realized these data (x, y, z) are not sent in order. I put counter per data and realized they are not in synch.

18 Nov 09:56:28 - [info] [BLE Connect:...] x = 24
18 Nov 09:56:28 - [info] [BLE Connect:... y = 30
18 Nov 09:56:28 - [info] [BLE Connect:...] z = 28

So while, x is sent 24 times, y is sent 30 times. Hence I would like to create only 1 characteristic and send X, Y, Z together. However, I believe, we can't send arrays. How should I proceed?

I would start by posting the code in your Arduino and Pi.

Sure @Grumpy_Mike ,

My full Arduino code:

#include <ArduinoBLE.h>
#include <Arduino_HTS221.h>
#include <Arduino_LPS22HB.h>
#include <Arduino_LSM9DS1.h>

#define BLE_UUID_ENVIRONMENTAL_SENSING_SERVICE    "181A"
#define BLE_UUID_TEMPERATURE                      "2A6E"
#define BLE_UUID_HUMIDITY                         "2A6F"
#define BLE_UUID_PRESSURE                         "2A6D"
#define BLE_UUID_ACCELEROMETER_SERVICE            "1101"
#define BLE_UUID_ACCELEROMETER_X                  "2101"
#define BLE_UUID_ACCELEROMETER_Y                  "2102"
#define BLE_UUID_ACCELEROMETER_Z                  "2103"

#define BLE_DEVICE_NAME                           "Hako"
#define BLE_LOCAL_NAME                            "Hako"

BLEService environmentalSensingService(BLE_UUID_ENVIRONMENTAL_SENSING_SERVICE);
BLEService accelerometerService(BLE_UUID_ACCELEROMETER_SERVICE);


BLEShortCharacteristic temperatureCharacteristic(BLE_UUID_TEMPERATURE, BLERead | BLENotify);
BLEUnsignedShortCharacteristic humidityCharacteristic(BLE_UUID_HUMIDITY, BLERead | BLENotify);
BLEUnsignedLongCharacteristic pressureCharacteristic(BLE_UUID_PRESSURE, BLERead | BLENotify);
BLEShortCharacteristic accelerometerCharacteristic_X(BLE_UUID_ACCELEROMETER_X, BLERead | BLENotify);
BLEShortCharacteristic accelerometerCharacteristic_Y(BLE_UUID_ACCELEROMETER_Y, BLERead | BLENotify);
BLEShortCharacteristic accelerometerCharacteristic_Z(BLE_UUID_ACCELEROMETER_Z, BLERead | BLENotify);

#define ENV_SENSOR_UPDATE_INTERVAL                (1000)
#define ACC_SENSOR_UPDATE_INTERVAL                (500)

typedef struct __attribute__((packed))
{
  float temperature;
  float humidity;
  float pressure;
  bool updated = false;
} env_sensor_data_t;

env_sensor_data_t envSensorData;

typedef struct __attribute__((packed))
{
  float accX;
  float accY;
  float accZ;
  bool updated = false;
} acc_sensor_data_t;

acc_sensor_data_t accSensorData;

#define BLE_LED_PIN                               LED_BUILTIN


void setup()
{
  Serial.begin(9600);
  while (!Serial);
  Serial.println( "BLE Example - Environmental Sensing Service (ESS)" );
  pinMode( BLE_LED_PIN, OUTPUT );
  digitalWrite( BLE_LED_PIN, LOW );
  // Without Serial when using USB power bank HTS sensor seems to needs some time for setup
  delay(10);
  if (!HTS.begin()) {
    Serial.println("Failed to initialize humidity temperature sensor!");
    while (1);
  }
  if (!BARO.begin()) {
    Serial.println( "Failed to initialize pressure sensor!" );
    while (1);
  }
  if (!IMU.begin()) {
    Serial.println("Failed to initialize IMU!");
    while (1);
  }
  if (!setupBleMode()) {
    while (1);
  }
  else {
    Serial.println( "BLE initialized. Waiting for clients to connect." );
  }
}

void loop() {
  bleTask();
  if (envSensorTask()) {
    envPrintTask();
  }
  if (accSensorTask()) {
    accPrintTask();
  }
}

bool envSensorTask() {
  static long previousMillis = 0;
  unsigned long currentMillis = millis(); 
  if (currentMillis - previousMillis < ENV_SENSOR_UPDATE_INTERVAL) {
    return false;
  }
  previousMillis = currentMillis;
  envSensorData.temperature = HTS.readTemperature();
  envSensorData.humidity = HTS.readHumidity();
  envSensorData.pressure = BARO.readPressure() * 1000; // kPa -> Pa
  envSensorData.updated = true;
  return envSensorData.updated;
}

bool accSensorTask() {
  static long previousMillis2 = 0;
  unsigned long currentMillis2 = millis();
  static float x = 0.00, y = 0.00, z = 0.00;
  if (currentMillis2 - previousMillis2 < ACC_SENSOR_UPDATE_INTERVAL) {
    return false;
  }
  previousMillis2 = currentMillis2;
  if(IMU.accelerationAvailable()){
    IMU.readAcceleration(x, y, z);
    accSensorData.accX = x;
    accSensorData.accY = y;
    accSensorData.accZ = z;
    accSensorData.updated = true;
  }
  return accSensorData.updated;
}

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

  // set advertised local name and service UUID
  BLE.setDeviceName(BLE_DEVICE_NAME);
  BLE.setLocalName(BLE_LOCAL_NAME);
  BLE.setAdvertisedService(environmentalSensingService);
  BLE.setAdvertisedService(accelerometerService);

  // BLE add characteristics
  environmentalSensingService.addCharacteristic(temperatureCharacteristic);
  environmentalSensingService.addCharacteristic(humidityCharacteristic);
  environmentalSensingService.addCharacteristic(pressureCharacteristic);
  accelerometerService.addCharacteristic(accelerometerCharacteristic_X);
  accelerometerService.addCharacteristic(accelerometerCharacteristic_Y);
  accelerometerService.addCharacteristic(accelerometerCharacteristic_Z);
  

  // add service
  BLE.addService(environmentalSensingService);
  BLE.addService(accelerometerService);

  // set the initial value for the characeristics
  temperatureCharacteristic.writeValue(0);
  humidityCharacteristic.writeValue(0);
  pressureCharacteristic.writeValue(0);
  accelerometerCharacteristic_X.writeValue(0);
  accelerometerCharacteristic_Y.writeValue(0);
  accelerometerCharacteristic_Z.writeValue(0);

  // set BLE event handlers
  BLE.setEventHandler(BLEConnected, blePeripheralConnectHandler);
  BLE.setEventHandler(BLEDisconnected, blePeripheralDisconnectHandler);

  // start advertising
  BLE.advertise();

  return true;
}

void bleTask()
{
  const uint32_t BLE_UPDATE_INTERVAL = 10;
  static uint32_t previousMillis = 0;
  uint32_t currentMillis = millis();
  if (currentMillis - previousMillis >= BLE_UPDATE_INTERVAL) {
    previousMillis = currentMillis;
    BLE.poll();
  }

  if (envSensorData.updated) {

    int16_t temperature = round(envSensorData.temperature * 100.0);
    temperatureCharacteristic.writeValue(temperature);

    uint16_t humidity = round(envSensorData.humidity * 100.0);
    humidityCharacteristic.writeValue(humidity);

    uint32_t pressure = round(envSensorData.pressure * 10.0);
    pressureCharacteristic.writeValue(pressure);
    envSensorData.updated = false;
  }

  if (accSensorData.updated) {
    // BLE does not define accelerometer UUID
    // Alls units is in G
    int16_t accelerometer_X = round(accSensorData.accX * 100.0);
    accelerometerCharacteristic_X.writeValue(accelerometer_X);
    
    int16_t accelerometer_Y = round(accSensorData.accY * 100.0);
    accelerometerCharacteristic_Y.writeValue(accelerometer_Y);

    int16_t accelerometer_Z = round(accSensorData.accZ * 100.0);
    accelerometerCharacteristic_Z.writeValue(accelerometer_Z);
 
    envSensorData.updated = false;
  }
}

/*
 * Print tasks that allow us to monitor sensor data
 * Useful for testing, not needed later on
 */

void envPrintTask() {
  Serial.print( "Temperature = " );
  Serial.print( envSensorData.temperature );
  Serial.println( " °C" );

  Serial.print( "Humidity    = " );
  Serial.print( envSensorData.humidity );
  Serial.println( " %" );

  Serial.print( "Pressure = " );
  Serial.print( envSensorData.pressure );
  Serial.println( " Pa" );

  Serial.print(temperatureCharacteristic.subscribed());
  Serial.print(humidityCharacteristic.subscribed());
  Serial.println(pressureCharacteristic.subscribed());
}

void accPrintTask() {
  Serial.print("AccX = ");
  Serial.print(accSensorData.accX);
  Serial.println(" G");

  Serial.print("AccY = ");
  Serial.print(accSensorData.accY);
  Serial.println(" G");

  Serial.print("AccZ = ");
  Serial.print( accSensorData.accZ );
  Serial.println(" G");

  Serial.print(accelerometerCharacteristic_X.subscribed());
  Serial.print(accelerometerCharacteristic_Y.subscribed());
  Serial.println(accelerometerCharacteristic_Z.subscribed());
}

void blePeripheralConnectHandler(BLEDevice central) {
  digitalWrite(BLE_LED_PIN, HIGH);
  Serial.print(F( "Connected to central: " ));
  Serial.println(central.address());
}

void blePeripheralDisconnectHandler( BLEDevice central ) {
  digitalWrite(BLE_LED_PIN, LOW);
  Serial.print(F("Disconnected from central: "));
  Serial.println(central.address());
}

I am writing on Node.js in Raspberry Pi, hence the code is too long, I will only post related part, where I read data. What I do here is, I first set notification bit to subscribe, then I read data:

for (const [key, character] of Object.entries(ALL.characteristics)) {

                    // Check the notify bit, if not set, set it. //
                    if (character.properties.includes("notify")) {
                        const descriptors = await character.discoverDescriptorsAsync().catch(e => send(e));
                        for (const [key, descriptor] of Object.entries(descriptors)) {
                            node.log(descriptor);
                            let descriptorData = await descriptor.readValueAsync().catch(e => send(e));
                            if (descriptorData[0] === bufferChecker[0] || descriptorData[1] === bufferChecker [1]) {
                                node.log(`The ${character.name} ${character.uuid} notify bit is disabled.`);
                                node.log("Enabling notification bit...");
                                descriptor.writeValueAsync(notifySetter).catch(e => send(e));
                                node.log (`Notification for ${character.name} characteristic is enabled.`);
                            } else {
                                node.log(`The ${character.name} ${character.uuid} notify bit is already enabled.`);
                                return;
                            }
                        }
                    } else {
                        node.log(`Notification is not allowed for ${character.name} characteristic.`)
                    }
                }

                for (const [key, character] of Object.entries(ALL.characteristics)) {
                    character.on('data', (data) => {
                        if (character.uuid === '2a6d') {
                            data = data.readUInt32LE() * decimalSetter[1];
                            environmentalData.payload[character.name] = data.toFixed(2);
                            counterPres++;
                        } else if (character.uuid === '2a6e') {
                            data = data.readUInt16LE() * decimalSetter[0];
                            environmentalData.payload[character.name] = data.toFixed(2);
                            counterTemp++;
                        } else if (character.uuid === '2a6f') {
                            data = data.readUInt16LE() * decimalSetter[0];
                            environmentalData.payload[character.name] = data.toFixed(2);
                            counterHum++;
                        } else if (character.uuid === '2101') {
                            data = data.readInt16LE() * decimalSetter[0];
                            accData.payload[character.name] = data.toFixed(2);
                            counterAccX++;
                        } else if (character.uuid === '2102') {
                            data = data.readInt16LE() * decimalSetter[0];
                            accData.payload[character.name] = data.toFixed(2);
                            counterAccY++;
                        } else if (character.uuid === '2103') {
                            data = data.readInt16LE() * decimalSetter[0];
                            accData.payload[character.name] = data.toFixed(2);
                            counterAccZ++;
                        }
                        node.log("x = " + counterAccX);
                        node.log("y = " + counterAccY);
                        node.log("z = " + counterAccZ);
                        // Sends Temp., Hum., and Pres. data together.
                        if ( (counterHum + counterPres + counterTemp) % 3 == 0 && (counterHum + counterPres + counterTemp) !== 0){
                            send(environmentalData);
                        }
                      });
                    // Character data event listener END //
                }

Sorry I know nothing about that. You will have to wait until someone who does can reply.
I have changed your thread's title so others will know what to expect.

In a prior project, I needed Arduino sensor data in Raspberry Pi and the ability to average, filter, graph, and web. Here is how I did it:

https://www.stm32duino.com/viewtopic.php?t=959

Ray

Hi @mrburnette,

Thanks for sharing this, but I need to send data over BLE.

BLE is an input capability of NodeRed:
https://flows.nodered.org/node/node-red-contrib-noble-bluetooth

BLE is a transport protocol:
https://www.youtube.com/watch?v=KhCWqdTnGBc

Hi @mrburnette,

Yes I know these, thanks for sharing. I am also using node-red as well.

I am a developer of the following package: https://www.npmjs.com/package/node-red-contrib-ble-sense.

I have one Arduino Nano 33 BLE Sense. This module has built-in IMU (accelerometer, magnetometer, gyroscope) sensor. So, I am looking for the best way to send these data from Arduino to Pi over BLE.

Hi @Grumpy_Mike,

I've realized now the title is kind of confusing.

"How to send acc. data from Arduino Nano 33 BLE Sense to Pi?" or "What is the most efficient way to send acc. data from Arduino to Pi?" would make more sense.

Because you originated this thread, you can change it to anything you want, just edit the first post and change the title. Only moderators or people with a trust level of 3 can change the title or move a post, as well as the original poster.

You may wish to look at Struct_send and Struct_receive:

https://github.com/LowPowerLab/RFM69/tree/master/Examples

@mrburnette many thanks for this it put me in the right direction.

I found a nice example from @Klaus_K in this forum as well.

I will post my final code, when I am done.

This is the final code as I promised. It allows me to transfer IMU data without any visible issues. I did not observe any issues related transfer rate as well. It works pretty fine at max freqs. It is mostly based on @Klaus_K previous codes, many thanks for those. I appreciate any advice:

/*
 * Example to send all data in one struct
 */

#include <ArduinoBLE.h>
#include <Arduino_LSM9DS1.h>

//----------------------------------------------------------------------------------------------------------------------
// BLE UUIDS
//----------------------------------------------------------------------------------------------------------------------

#define BLE_UUID_IMU_SERVICE              "1101"
#define BLE_UUID_ACC_CHAR                 "2101"
#define BLE_UUID_GYRO_CHAR                "2102"
#define BLE_UUID_MAG_CHAR                 "2103"

//----------------------------------------------------------------------------------------------------------------------
// APP & I/O
//----------------------------------------------------------------------------------------------------------------------

//#define NUMBER_OF_SENSORS 3

#define ACC_SENSOR_UPDATE_INTERVAL                (500) // node-red-dashboard can't handle 1 ms, can handle 100 ms.
#define MAG_SENSOR_UPDATE_INTERVAL                (500)
#define GYRO_SENSOR_UPDATE_INTERVAL               (500)

union sensor_data {
  struct __attribute__((packed)) {
    float values[3]; // float array for data (it holds 3)
    bool updated = false;
  };
  uint8_t bytes[3 * sizeof(float)]; // size as byte array 
};

union sensor_data accData;
union sensor_data gyroData;
union sensor_data magData;


//const int BLE_LED_PIN = LED_BUILTIN;
//const int RSSI_LED_PIN = LED_PWR;
//----------------------------------------------------------------------------------------------------------------------
// BLE
//----------------------------------------------------------------------------------------------------------------------

#define BLE_DEVICE_NAME                           "Hako"
#define BLE_LOCAL_NAME                            "Hako"

BLEService IMUService(BLE_UUID_IMU_SERVICE);
BLECharacteristic accCharacteristic(BLE_UUID_ACC_CHAR, BLERead | BLENotify, sizeof accData.bytes);
BLECharacteristic gyroCharacteristic(BLE_UUID_GYRO_CHAR, BLERead | BLENotify, sizeof gyroData.bytes);
BLECharacteristic magCharacteristic(BLE_UUID_MAG_CHAR, BLERead | BLENotify, sizeof magData.bytes);

#define BLE_LED_PIN                               LED_BUILTIN

//----------------------------------------------------------------------------------------------------------------------
// SETUP
//----------------------------------------------------------------------------------------------------------------------

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

  Serial.println("BLE Example - IMU Service (ESS)");
  pinMode(BLE_LED_PIN, OUTPUT);
  digitalWrite(BLE_LED_PIN, LOW);
  
  if (!IMU.begin()) {
    Serial.println("Failed to initialize IMU!");
    while (1);
  }
  if (!setupBleMode()) {
    while (1);
  } else {
    Serial.println("BLE initialized. Waiting for clients to connect.");
  }
  
  // write initial value
  for (int i = 0; i < 3; i++) {
    accData.values[i] = i;
    gyroData.values[i] = i;
    magData.values[i] = i;
  }
}

void loop() {
  
  bleTask();
  if (accSensorTask()) {
    accPrintTask();
  }
  if (gyroSensorTask()){
    gyroPrintTask();
  }
  if (magSensorTask()){
    magPrintTask();
  }
}

//----------------------------------------------------------------------------------------------------------------------
// SENSOR TASKS
/*
 * We define bool function for each sensor.
 * Function returns true if sensor data are updated.
 * Allows us to define different update intervals per sensor data.
 */
//----------------------------------------------------------------------------------------------------------------------

bool accSensorTask() {
  static long previousMillis2 = 0;
  unsigned long currentMillis2 = millis();
  static float x = 0.00, y = 0.00, z = 0.00;
  if (currentMillis2 - previousMillis2 < ACC_SENSOR_UPDATE_INTERVAL) {
    return false;
  }
  previousMillis2 = currentMillis2;
  if(IMU.accelerationAvailable()){
    IMU.readAcceleration(x, y, z);
    accData.values[0] = x; // 0.11
    accData.values[1] = y; // 1.13
    accData.values[2] = z; // -1.13
    accData.updated = true;
  }
  return accData.updated;
}

bool gyroSensorTask() {
  static long previousMillis2 = 0;
  unsigned long currentMillis2 = millis();
  static float x = 0.00, y = 0.00, z = 0.00;
  if (currentMillis2 - previousMillis2 < GYRO_SENSOR_UPDATE_INTERVAL) {
    return false;
  }
  previousMillis2 = currentMillis2;
  if(IMU.gyroscopeAvailable()){
    IMU.readGyroscope(x, y, z);
    gyroData.values[0] = x; // 0.11
    gyroData.values[1] = y; // 1.13
    gyroData.values[2] = z; // -1.13
    gyroData.updated = true;
  }
  return gyroData.updated;
}

bool magSensorTask() {
  static long previousMillis3 = 0;
  unsigned long currentMillis3 = millis();
  static float x = 0.00, y = 0.00, z = 0.00;
  if (currentMillis3 - previousMillis3 < MAG_SENSOR_UPDATE_INTERVAL) {
    return false;
  }
  previousMillis3 = currentMillis3;
  if(IMU.magneticFieldAvailable()){
    IMU.readMagneticField(x, y, z);
    magData.values[0] = x; // 0.11
    magData.values[1] = y; // 1.13
    magData.values[2] = z; // -1.13
    magData.updated = true;
  }
  return magData.updated;
}

//----------------------------------------------------------------------------------------------------------------------
//  BLE SETUP
/*
 * Determine which services/characteristics to be advertised.
 * Determine the device name.
 * Set event handlers.
 * Set inital value for characteristics.
 */
//----------------------------------------------------------------------------------------------------------------------

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

  // set advertised local name and service UUID:
  BLE.setDeviceName(BLE_DEVICE_NAME);
  BLE.setLocalName(BLE_LOCAL_NAME);
  BLE.setAdvertisedService(IMUService);

  // BLE add characteristics
  IMUService.addCharacteristic(accCharacteristic);
  IMUService.addCharacteristic(gyroCharacteristic);
  IMUService.addCharacteristic(magCharacteristic);

  // add service
  BLE.addService(IMUService);

  // set the initial value for the characteristic:
  accCharacteristic.writeValue(accData.bytes, sizeof accData.bytes);
  gyroCharacteristic.writeValue(gyroData.bytes, sizeof gyroData.bytes);
  magCharacteristic.writeValue(magData.bytes, sizeof magData.bytes);

  // set BLE event handlers
  BLE.setEventHandler(BLEConnected, blePeripheralConnectHandler);
  BLE.setEventHandler(BLEDisconnected, blePeripheralDisconnectHandler);

  // start advertising
  BLE.advertise();

  return true;
}

void bleTask()
{
  const uint32_t BLE_UPDATE_INTERVAL = 10;
  static uint32_t previousMillis = 0;
  uint32_t currentMillis = millis();
  if (currentMillis - previousMillis >= BLE_UPDATE_INTERVAL) {
    previousMillis = currentMillis;
    BLE.poll();
  }
  if (accData.updated) {
    // Bluetooth does not define accelerometer
    // Units in G
    int16_t accelerometer_X = round(accData.values[0] * 100.0);
    int16_t accelerometer_Y = round(accData.values[1] * 100.0);
    int16_t accelerometer_Z = round(accData.values[2] * 100.0);
    accCharacteristic.writeValue(accData.bytes, sizeof accData.bytes);
    accData.updated = false;
  }

  if (gyroData.updated) {
    // Bluetooth does not define gyroscope
    // Units in dps
    int16_t gyro_X = round(gyroData.values[0] * 100.0);
    int16_t gyro_Y = round(gyroData.values[1] * 100.0);
    int16_t gyro_Z = round(gyroData.values[2] * 100.0);
    gyroCharacteristic.writeValue(gyroData.bytes, sizeof gyroData.bytes);
    gyroData.updated = false;
  }

  if (magData.updated) {
    // Bluetooth does not define accelerometer UUID
    // Units in uT
    int16_t mag_X = round(magData.values[0] * 100.0);
    int16_t mag_Y = round(magData.values[1] * 100.0);
    int16_t mag_Z = round(magData.values[2] * 100.0);
    magCharacteristic.writeValue(magData.bytes, sizeof magData.bytes);
    magData.updated = false;
  }
}

//----------------------------------------------------------------------------------------------------------------------
// PRINT TASKS
/*
 * Print tasks per sensor type.
 * Useful to test accuracy of sensor data before sending over BLE.
 */
//----------------------------------------------------------------------------------------------------------------------

void accPrintTask() {
  Serial.print("AccX = ");
  Serial.print(accData.values[0]);
  Serial.println(" G");

  Serial.print("AccY = ");
  Serial.print(accData.values[1]);
  Serial.println(" G");

  Serial.print("AccZ = ");
  Serial.print(accData.values[2]);
  Serial.println(" G");

  Serial.print("Acc. Subscription Status: ");
  Serial.println(accCharacteristic.subscribed());
}

void gyroPrintTask() {
  Serial.print("gyroX = ");
  Serial.print(gyroData.values[0]);
  Serial.println(" dps");

  Serial.print("gyroY = ");
  Serial.print(gyroData.values[1]);
  Serial.println(" dps");

  Serial.print("gyroZ = ");
  Serial.print(gyroData.values[2]);
  Serial.println(" dps");

  Serial.print("Gyro. Subscription Status: ");
  Serial.println(gyroCharacteristic.subscribed());
}

void magPrintTask() {
  Serial.print("magX = ");
  Serial.print(magData.values[0]);
  Serial.println(" uT");

  Serial.print("magY = ");
  Serial.print(magData.values[1]);
  Serial.println(" uT");

  Serial.print("magZ = ");
  Serial.print(magData.values[2]);
  Serial.println(" uT");

  Serial.print("Mag. Subscription Status: ");
  Serial.println(magCharacteristic.subscribed());
}

//----------------------------------------------------------------------------------------------------------------------
// Event Handlers
/*
 * These are handlers that inform connection status
 * Useful when testing, might be removed later on.
 */
//----------------------------------------------------------------------------------------------------------------------

void blePeripheralConnectHandler(BLEDevice central) {
  digitalWrite(BLE_LED_PIN, HIGH);
  Serial.print(F( "Connected to central: " ));
  Serial.println(central.address());
}

void blePeripheralDisconnectHandler(BLEDevice central) {
  digitalWrite(BLE_LED_PIN, LOW);
  Serial.print(F("Disconnected from central: "));
  Serial.println(central.address());
}

One comment regarding the UUIDs. The BLE specification says you must use 128-bit random UUIDs for self-defined services and characteristics. 16-bit UUIDs are reserved for use by the Bluetooth SIG. Some of my examples use 16-bit UUIDs but only when they are defined by the Bluetooth SIG. So, any device that knows about the service implemented can make use of them.

You can create the UUIDs with an online generator. Google "UUID generator" to find a few. You can also write yourself a script in the scripting language of your choice.