I am trying to send a real-time temperature data (float) from Arduino Nano 33 BLE Sense to Raspberry Pi 4B over BLE connection. I already have succeeded to transmit the data with broadcasting. So, this time I want to establish a connection between central and peripheral and send the data. However, there are few blurry things that I could not understand:
1 - The BLECharacteristic() method has float version BLEFloatCharacteristic so I am using that one as given here. However, the writeValue comes with two versions:
So does that mean the value can be a float if my characteristic is a float as well or I should send byte array and try the workaround that is mentioned here?
2 - I have also given read | notify permission to read the data continuously as seen below:
Yes, you can write the float value. The buffer and length variant can be used for custom type characteristics.
However, may I recommend, not using float data types unless necessary. Here are a few points that may help you make your own choice.
The BLE interface (service and characteristic) is public, and you should choose it independent of the implementation. This will allow you to change the sensor and the library which may use a different data type and still use the same BLE app because the interface can stay the same.
The Bluetooth SIG has specified an Environmental Sensing Service and characteristics using a fixed-point system by specifying an exponent.
If you just use them, you can use an app that knows how to decode the data. e.g., EFR Connect on iOS (maybe Android, I did not test)
Here is an example of this service for the Arduino Nano 33 BLE Sense using the onboard sensors. As you can see, I used float to store the values internally and use the BLE specified type for the BLE characteristics.
Arduino Nano 33 BLE ESS Example (click to view)
/*
This example creates a BLE peripheral with a Environmental Sensing Service (ESS)
that contains a temperature, humidity and pressure characteristic.
The yellow LED shows the BLE module is connected to a central.
The circuit:
- Arduino Nano 33 BLE Sense
You can use a generic BLE central app, like BLE Scanner (iOS and Android) or
nRF Connect, to interact with the services and characteristics
created in this sketch.
This example code is in the public domain.
*/
#include <ArduinoBLE.h>
#include <Arduino_HTS221.h>
#include <Arduino_LPS22HB.h>
//----------------------------------------------------------------------------------------------------------------------
// BLE UUIDs
//----------------------------------------------------------------------------------------------------------------------
// https://www.bluetooth.com/specifications/assigned-numbers/environmental-sensing-service-characteristics/
// https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.temperature.xml
// https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.humidity.xml
// https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.pressure.xml
#define BLE_UUID_ENVIRONMENTAL_SENSING_SERVICE "181A"
#define BLE_UUID_TEMPERATURE "2A6E"
#define BLE_UUID_HUMIDITY "2A6F"
#define BLE_UUID_PRESSURE "2A6D"
//----------------------------------------------------------------------------------------------------------------------
// BLE
//----------------------------------------------------------------------------------------------------------------------
#define BLE_DEVICE_NAME "Arduino Nano 33 BLE"
#define BLE_LOCAL_NAME "Arduino Nano 33 BLE"
BLEService environmentalSensingService( BLE_UUID_ENVIRONMENTAL_SENSING_SERVICE );
BLEShortCharacteristic temperatureCharacteristic( BLE_UUID_TEMPERATURE, BLERead | BLENotify );
BLEUnsignedShortCharacteristic humidityCharacteristic( BLE_UUID_HUMIDITY, BLERead | BLENotify );
BLEUnsignedLongCharacteristic pressureCharacteristic( BLE_UUID_PRESSURE, BLERead | BLENotify );
//----------------------------------------------------------------------------------------------------------------------
// APP & I/O
//----------------------------------------------------------------------------------------------------------------------
#define SENSOR_UPDATE_INTERVAL (5000)
typedef struct __attribute__( ( packed ) )
{
float temperature;
float humidity;
float pressure;
bool updated = false;
} sensor_data_t;
sensor_data_t sensorData;
#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 ( !setupBleMode() )
{
Serial.println( "Failed to initialize BLE!" );
while ( 1 );
}
else
{
Serial.println( "BLE initialized. Waiting for clients to connect." );
}
}
void loop()
{
bleTask();
if ( sensorTask() )
{
printTask();
}
}
bool sensorTask()
{
static long previousMillis = 0;
unsigned long currentMillis = millis();
if ( currentMillis - previousMillis < SENSOR_UPDATE_INTERVAL )
{
return false;
}
previousMillis = currentMillis;
sensorData.temperature = HTS.readTemperature();
sensorData.humidity = HTS.readHumidity();
sensorData.pressure = BARO.readPressure() * 1000; // kPa -> Pa
sensorData.updated = true;
return sensorData.updated;
}
void printTask()
{
Serial.print( "Temperature = " );
Serial.print( sensorData.temperature );
Serial.println( " °C" );
Serial.print( "Humidity = " );
Serial.print( sensorData.humidity );
Serial.println( " %" );
Serial.print( "Pressure = " );
Serial.print( sensorData.pressure );
Serial.println( " Pa" );
}
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 add characteristics
environmentalSensingService.addCharacteristic( temperatureCharacteristic );
environmentalSensingService.addCharacteristic( humidityCharacteristic );
environmentalSensingService.addCharacteristic( pressureCharacteristic );
// add service
BLE.addService( environmentalSensingService );
// set the initial value for the characeristic
temperatureCharacteristic.writeValue( 0 );
humidityCharacteristic.writeValue( 0 );
pressureCharacteristic.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 ( sensorData.updated )
{
// BLE defines Temperature UUID 2A6E Type sint16 ( see XML links )
// Unit is in degrees Celsius with a resolution of 0.01 degrees Celsius
int16_t temperature = round( sensorData.temperature * 100.0 );
temperatureCharacteristic.writeValue( temperature );
// BLE defines Humidity UUID 2A6F Type uint16
// Unit is in percent with a resolution of 0.01 percent
uint16_t humidity = round( sensorData.humidity * 100.0 );
humidityCharacteristic.writeValue( humidity );
// BLE defines Pressure UUID 2A6D Type uint32
// Unit is in Pascal with a resolution of 0.1 Pa
uint32_t pressure = round( sensorData.pressure * 10.0 );
pressureCharacteristic.writeValue( pressure );
sensorData.updated = false;
}
}
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() );
}
Here are a few more general information and arguments.
Float is a data type that has been implemented in multiple formats. e.g., IEEE754 (used by ARM processors) and IEEE 11073 used for medical devices.
FP data types are hard to read and to convert if it is not the native type supported by the platform
Both data types have been specified for the Characteristics Presentation Format by the Bluetooth SIG. So, if you must, you can use them and even inform the central.
Thank you for these great advices and an example. Currently my Arduino is busy with generating a dataset, as soon as it finishes, I will adjust my code according to your example. I was unaware of Environmental Sensing Service.
I have found the document that defines UUID characteristics. However I could not find the mention of Type. So can you share the XML links that you mention in your code here:
// BLE defines Temperature UUID 2A6E Type sint16 ( see XML links )
In my case, I am not using any specific app. I am reading the data in Node-RED hosted in Raspberry Pi, that allows you to create custom nodes based on JavaScript to develop applications.
I am using the @abandonware/noble as in my case Pi is the central. So data will be read by pure JavaScript script.
So here what you store is float, what you send is either short or long if I understand right. For example the data you store is float (24.541) what you send is short (2454). I can do the same and convert the data on Node-RED. I will update as soon as I can.
I am not able to see any float support mentioned in the noble.js as well.
The links are at the top of the document in the BLE UUIDs section. They contain the UUID and format description.
How did you do that? I can use BLE on the Pi with some CMD line tool but the BLE support in Node RED seems to have access right issues or something like that. I have Raspberry Pi 3 and 4 with Raspberry Pi OS.
Thank you, I will try this. I already found a note on one of the pages that some privileges need to be set for this to work. I will see whether I can follow the instruction successfully.
That points toward an issue on the Pi. Your Arduino sketch is working with the app.
How did you come to that conclusion? When you set BLENotify for the characteristic, the descriptors are added by the ArduinoBLE library for you. This is how the BLE app on your phone knows it can describe to the charateristic.
There are functions to add descriptors in the ArduinoBLE library. But that is only for optional descriptors. Unfortunately, they are mostly ignored by BLE apps.
@Klaus_K I have tried to debug further. The sudo btmon log of the Pi as follows:
> HCI Event: LE Meta Event (0x3e) plen 18 #101 [hci0] 216.091528
LE Advertising Report (0x02)
Num reports: 1
Event type: Scan response - SCAN_RSP (0x04)
Address type: Public (0x00)
Address: 09:16:8C:47:5F:71 (OUI 09-16-8C)
Data length: 6
Name (complete): Nano
RSSI: -73 dBm (0xb7)
< HCI Command: LE Set Scan Enable (0x08|0x000c) plen 2 #166 [hci0] 20.855945
Scanning: Disabled (0x00)
Filter duplicates: Enabled (0x01)
> HCI Event: Command Complete (0x0e) plen 4 #167 [hci0] 20.856820
LE Set Scan Enable (0x08|0x000c) ncmd 1
Status: Success (0x00)
< HCI Command: LE Create Connection (0x08|0x000d) plen 25 #168 [hci0] 20.861549
Scan interval: 60.000 msec (0x0060)
Scan window: 30.000 msec (0x0030)
Filter policy: White list is not used (0x00)
Peer address type: Public (0x00)
Peer address: 09:16:8C:47:5F:71 (OUI 09-16-8C)
Own address type: Public (0x00)
Min connection interval: 7.50 msec (0x0006)
Max connection interval: 15.00 msec (0x000c)
Connection latency: 0 (0x0000)
Supervision timeout: 2000 msec (0x00c8)
Min connection length: 2.500 msec (0x0004)
Max connection length: 3.750 msec (0x0006)
> HCI Event: Command Status (0x0f) plen 4 #169 [hci0] 20.862080
LE Create Connection (0x08|0x000d) ncmd 1
Status: Success (0x00)
> HCI Event: LE Meta Event (0x3e) plen 19 #170 [hci0] 21.168187
LE Connection Complete (0x01)
Status: Success (0x00)
Handle: 64
Role: Master (0x00)
Peer address type: Public (0x00)
Peer address: 09:16:8C:47:5F:71 (OUI 09-16-8C)
Connection interval: 15.00 msec (0x000c)
Connection latency: 0 (0x0000)
Supervision timeout: 2000 msec (0x00c8)
Master clock accuracy: 0x00
@ MGMT Event: Device Connected (0x000b) plen 13 {0x0002} [hci0] 21.168288
LE Address: 09:16:8C:47:5F:71 (OUI 09-16-8C)
Flags: 0x00000000
Data length: 0
@ MGMT Event: Device Connected (0x000b) plen 13 {0x0001} [hci0] 21.168288
LE Address: 09:16:8C:47:5F:71 (OUI 09-16-8C)
Flags: 0x00000000
Data length: 0
< HCI Command: LE Read Remote Used Features (0x08|0x0016) plen 2 #171 [hci0] 21.168529
Handle: 64
> HCI Event: Command Status (0x0f) plen 4 #172 [hci0] 21.169549
LE Read Remote Used Features (0x08|0x0016) ncmd 1
Status: Success (0x00)
> HCI Event: Command Complete (0x0e) plen 14 #173 [hci0] 21.169670
LE Read Remote Used Features (0x08|0x0016) ncmd 1
Status: Success (0x00)
00 00 00 00 00 00 00 00 00 00 ..........
> HCI Event: LE Meta Event (0x3e) plen 12 #174 [hci0] 21.187752
LE Read Remote Used Features (0x04)
Status: Success (0x00)
Handle: 64
Features: 0x2f 0x00 0x00 0x00 0x00 0x00 0x00 0x00
LE Encryption
Connection Parameter Request Procedure
Extended Reject Indication
Slave-initiated Features Exchange
LE Data Packet Length Extension
< ACL Data TX: Handle 64 flags 0x00 dlen 7 #175 [hci0] 21.196651
ATT: Exchange MTU Request (0x02) len 2
Client RX MTU: 256
> ACL Data RX: Handle 64 flags 0x02 dlen 7 #176 [hci0] 21.232587
ATT: Exchange MTU Response (0x03) len 2
Server RX MTU: 247
< ACL Data TX: Handle 64 flags 0x00 dlen 11 #177 [hci0] 21.236291
ATT: Read By Group Type Request (0x10) len 6
Handle range: 0x0001-0xffff
Attribute group type: Primary Service (0x2800)
> HCI Event: Number of Completed Packets (0x13) plen 5 #178 [hci0] 21.262750
Num handles: 1
Handle: 64
Count: 2
> ACL Data RX: Handle 64 flags 0x02 dlen 24 #179 [hci0] 21.278151
ATT: Read By Group Type Response (0x11) len 19
Attribute data length: 6
Attribute group list: 3 entries
Handle range: 0x0001-0x0005
UUID: Generic Access Profile (0x1800)
Handle range: 0x0006-0x0009
UUID: Generic Attribute Profile (0x1801)
Handle range: 0x000a-0x0013
UUID: Environmental Sensing (0x181a)
< ACL Data TX: Handle 64 flags 0x00 dlen 11 #180 [hci0] 21.280267
ATT: Read By Group Type Request (0x10) len 6
Handle range: 0x0014-0xffff
Attribute group type: Primary Service (0x2800)
> ACL Data RX: Handle 64 flags 0x02 dlen 9 #181 [hci0] 21.307537
ATT: Error Response (0x01) len 4
Read By Group Type Request (0x10)
Handle: 0x0014
Error: Attribute Not Found (0x0a)
< ACL Data TX: Handle 64 flags 0x00 dlen 11 #182 [hci0] 21.310329
ATT: Read By Type Request (0x08) len 6
Handle range: 0x0001-0x0005
Attribute type: Characteristic (0x2803)
> HCI Event: Number of Completed Packets (0x13) plen 5 #183 [hci0] 21.322723
Num handles: 1
Handle: 64
Count: 2
> ACL Data RX: Handle 64 flags 0x02 dlen 20 #184 [hci0] 21.337681
ATT: Read By Type Response (0x09) len 15
Attribute data length: 7
Attribute data list: 2 entries
Handle: 0x0002
Value: 020300002a
Handle: 0x0004
Value: 020500012a
< ACL Data TX: Handle 64 flags 0x00 dlen 11 #185 [hci0] 21.340017
ATT: Read By Type Request (0x08) len 6
Handle range: 0x0006-0x0009
Attribute type: Characteristic (0x2803)
> ACL Data RX: Handle 64 flags 0x02 dlen 13 #186 [hci0] 21.367572
ATT: Read By Type Response (0x09) len 8
Attribute data length: 7
Attribute data list: 1 entry
Handle: 0x0007
Value: 200800052a
< ACL Data TX: Handle 64 flags 0x00 dlen 11 #187 [hci0] 21.368173
ATT: Read By Type Request (0x08) len 6
Handle range: 0x000a-0x0013
Attribute type: Characteristic (0x2803)
> HCI Event: Number of Completed Packets (0x13) plen 5 #188 [hci0] 21.382744
Num handles: 1
Handle: 64
Count: 2
> ACL Data RX: Handle 64 flags 0x02 dlen 27 #189 [hci0] 21.398165
ATT: Read By Type Response (0x09) len 22
Attribute data length: 7
Attribute data list: 3 entries
Handle: 0x000b
Value: 120c006e2a
Handle: 0x000e
Value: 120f006f2a
Handle: 0x0011
Value: 1212006d2a
< ACL Data TX: Handle 64 flags 0x00 dlen 11 #190 [hci0] 21.398694
ATT: Read By Type Request (0x08) len 6
Handle range: 0x0009-0x0009
Attribute type: Characteristic (0x2803)
> ACL Data RX: Handle 64 flags 0x02 dlen 9 #191 [hci0] 21.427558
ATT: Error Response (0x01) len 4
Read By Type Request (0x08)
Handle: 0x0009
Error: Attribute Not Found (0x0a)
< ACL Data TX: Handle 64 flags 0x00 dlen 11 #192 [hci0] 21.428283
ATT: Read By Type Request (0x08) len 6
Handle range: 0x0013-0x0013
Attribute type: Characteristic (0x2803)
> HCI Event: Number of Completed Packets (0x13) plen 5 #193 [hci0] 21.442725
Num handles: 1
Handle: 64
Count: 2
> ACL Data RX: Handle 64 flags 0x02 dlen 9 #194 [hci0] 21.472559
ATT: Error Response (0x01) len 4
Read By Type Request (0x08)
Handle: 0x0013
Error: Attribute Not Found (0x0a)
< ACL Data TX: Handle 64 flags 0x00 dlen 11 #195 [hci0] 21.474417
ATT: Read By Type Request (0x08) len 6
Handle range: 0x000b-0x000d
Attribute type: Client Characteristic Configuration (0x2902)
> ACL Data RX: Handle 64 flags 0x02 dlen 9 #196 [hci0] 21.502541
ATT: Error Response (0x01) len 4
Read By Type Request (0x08)
Handle: 0x000b
Error: Attribute Not Found (0x0a)
> HCI Event: Number of Completed Packets (0x13) plen 5 #197 [hci0] 21.661765
Num handles: 1
Handle: 64
Count: 1
If we have a look at the parts with errors first error comes when Pi tries to discover primary service. I am getting this error several times. I have no idea why Pi starts handle range from 0x0014:
< ACL Data TX: Handle 64 flags 0x00 dlen 11 #180 [hci0] 21.280267
ATT: Read By Group Type Request (0x10) len 6
Handle range: 0x0014-0xffff
Attribute group type: Primary Service (0x2800)
> ACL Data RX: Handle 64 flags 0x02 dlen 9 #181 [hci0] 21.307537
ATT: Error Response (0x01) len 4
Read By Group Type Request (0x10)
Handle: 0x0014
Error: Attribute Not Found (0x0a)
I interpreted this as there is no attribute with handle 0x0014 so it seems fine ( I am not 100% sure) as previously Pi can discover services as seen below when it scans for the all handle range:
< ACL Data TX: Handle 64 flags 0x00 dlen 11 #177 [hci0] 21.236291
ATT: Read By Group Type Request (0x10) len 6
Handle range: 0x0001-0xffff
Attribute group type: Primary Service (0x2800)
> HCI Event: Number of Completed Packets (0x13) plen 5 #178 [hci0] 21.262750
Num handles: 1
Handle: 64
Count: 2
> ACL Data RX: Handle 64 flags 0x02 dlen 24 #179 [hci0] 21.278151
ATT: Read By Group Type Response (0x11) len 19
Attribute data length: 6
Attribute group list: 3 entries
Handle range: 0x0001-0x0005
UUID: Generic Access Profile (0x1800)
Handle range: 0x0006-0x0009
UUID: Generic Attribute Profile (0x1801)
Handle range: 0x000a-0x0013
UUID: Environmental Sensing (0x181a)
Then it comes the error that prevents Pi from subscribing (I guess).
< ACL Data TX: Handle 64 flags 0x00 dlen 11 #195 [hci0] 21.474417
ATT: Read By Type Request (0x08) len 6
Handle range: 0x000b-0x000d
Attribute type: Client Characteristic Configuration (0x2902)
> ACL Data RX: Handle 64 flags 0x02 dlen 9 #196 [hci0] 21.502541
ATT: Error Response (0x01) len 4
Read By Type Request (0x08)
Handle: 0x000b
Error: Attribute Not Found (0x0a)
I am currently reading this guide to fully understand what is going on. So it seems like Pi is asking for a descriptor but there is none in the server (Arduino).
In return I would recommend you investigate gatttool and use them together.
The following line should give you all BLE device nearby, make note of the MAC address
sudo hcitool lescan
Start gatttool (replace x with MAC address
sudo gatttool -b xx:xx:xx:xx:xx:xx -I
Connect to your device
[xx:xx:xx:xx:xx:xx][LE]> connect
Get all services
[xx:xx:xx:xx:xx:xx][LE]> primary
Get all characteristics
[xx:xx:xx:xx:xx:xx][LE]> characteristics
Get all descriptors
[xx:xx:xx:xx:xx:xx][LE]> char-desc
The descriptors with 0x2902 in it are the ones for notifications. Note the handle for one of them. e.g., handle: 0x0009, uuid: 00002902-0000-1000-8000-00805f9b34fb
Side note: Compare the entries in the last table with the first two.
the descriptors with 2800 describe the services
the descriptors with 2803 describe the characteristics
the descriptors with 2902 describe the notifications
Read the descriptor using the handle
[xx:xx:xx:xx:xx:xx][LE]> char-read-hnd 0x0009
Write the descriptor using the handle (Enable the notification for that characteristic)
Thank you @Klaus_K for this post that enlightened me a lot. Few things regarding this that came to my mind:
How we can write even though we do not provide write permission when we define a characteristic such as BLEShortCharacteristic temperatureCharacteristic( BLE_UUID_TEMPERATURE, BLERead | BLENotify );?
I run the gatttool without sudo as i was curious maybe the problem in Pi was permission related, I exactly got the same results, so I think it isn't.
It seems like we need to enable notification bit via client, so BLENotify actually does not enable but let us enable via client. From the guide I am reading:
Every time a client wants to enable notifications or indications for a particular characteristic that supports them, it simply uses a Write Request ATT packet to set the corresponding bit to 1.
So the main reason of my problem is I am not able the set notification bit via client. The subscribe method in Noble.js is characteristic.subscribe([callback(error)]); does not ask for Write Request but ask for Read By Type Request.
I will try to figure out how can I set that bit via Noble.js then update the question.
Edit.: I also run all the commands in your post, I can get the notifications without any problem.
The BLERead sets the permission for the client. The client is allowed to read the data but not write. The peripheral aka server can write to the characteristic, otherwise now new data would ever be available.
There are two parts to it. The peripheral must allow notifications and the client needs to subscribe to receive notifications. Subscribing sets the value of the descriptor. So, yes BLENotify allows notifications to be used by the client. The client can choose not to use notifications but poll the data.
Thanks to your help @Klaus_K I figured it out.
This was the method I need: descriptor.writeValue(data[, callback(error)]);
Now I can subscribe and get notifications.