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?
#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.
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.
"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.
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.
You are definitely right. I decided to use microbit'scustom UUIDs.
Also, I made another slight change to the code. Now I print like this Serial.print(accData.values[0], 7); instead of this Serial.print(accData.values[0]); as I prefer to see the full data point rather than its 2 decimals version.
I actually changed my mind about the microbit UUID, I will just create a random one as @Klaus_K suggested. I will not delete the link as it might help someone.