This is an old revision of the document!
This scenario presents how to extend the Bluetooth Low Energy server and client devices with a notification or indication mechanism for sending data automatically. If enabled, notifications or indications are sent at any time while the data in the server is updated. A difference between them is that a notification is an unacknowledged message while an indication is an acknowledged message. While one of them is enabled by the client, the server decides on the time of the message sent.
It is necessary to understand the principles of the Bluetooth Low Energy protocol with concepts of services, characteristics and descriptors. We will use in this scenario the knowledge of the advertising process so making the STM_IoT_6: BLE Communication with characteristics exercise is recommended.
This scenario is intended to be implemented using two BLE laboratory nodes. One of them is a server, while the second is a client. Here we will present the extension of the scenario implemented in STM_IoT_6: BLE Communication with characteristics
Task 1. Implement a program that operates as the BLE server which advertises itself and allows us to connect to. We should be able to subscribe to notifications of the characteristic.
Task 2. Implement a client device, capable of subscribing to the characteristic and reading the exemplary data from a server with a notification mechanism.
It is advised to use the server and client programs from STM_IoT_6: BLE Communication with characteristics as the starting point.
We will pass through the lab in a few steps. We will add the second characteristic to the example from lab STM_IoT_6: BLE Communication with characteristics. We will configure this characteristic as notify/indicate capable. It allows us to establish a connection and, if successfully connected, enable the indication or notification feature. With this feature enabled, the peripheral device initiates data transmission. It can be used for periodic measurement reading or updating information on value change.
We start with the program written during the laboratory STM_IoT_6: BLE Communication with characteristics by adding another characteristic to the service and variable required to hold the pointer to the characteristic class. We need to define the UUID for this characteristic.
#define NOTIFY_CHARACTERISTIC_UUID "6e9b7b28-ca96-4774-b056-8ec5b759fd86" // BLE Characteristic - custom 128-bit UUID, read and notify enabled BLEByteCharacteristic pNotifyCharacteristic(NOTIFY_CHARACTERISTIC_UUID, BLERead | BLEWrite | BLENotify);
In the setup() function we add a new characteristic to the service and set its initial value.
// add the characteristic to the service pService.addCharacteristic(pNotifyCharacteristic); // set the initial value for the characteristic: pNotifyCharacteristic.setValue(1);
After these modifications, it would be possible to observe an additional characteristic in the service with the possibility of reading, writing, and enabling notifications.
At this step, we implement periodical data sending. We add the counter variable, an integer for holding the value incremented every loop pass.
int counter = 1;
In the main loop() we implement the incrementation of the counter and updating of the characteristic.
pCharacteristic.setValue(counter); counter++; delay(1000);
In this step, we will analyse the behaviour of the client. The client software is much more complex than the server. It is because the server, called also the central device, in many circumstances is a more powerful device than the peripheral. Some parts of the software are implemented as callback functions because they handle reactions on the data coming asynchronously from the server. The diagram presents the algorithm of the client and data coming from the server.
While we have analysed the client's behaviour we can start implementation. Let's begin with the libraries needed. Add the libraries to the platformio.ini.
lib_deps = stm32duino/STM32duinoBLE @ 1.2.6 arduino-libraries/LiquidCrystal@^1.0.7
And import libraries into the cpp file.
#include "Arduino.h" #include "STM32duinoBLE.h" #include "LiquidCrystal.h"
Next, we define the UUIDs for remote service and a characteristic. Notice they must match the ones defined in the server.
// The remote service we wish to connect to. #define REMOTE_SERVICE_UUID "6e9b7b26-ca96-4774-b056-8ec5b759fd86" // The characteristic of the remote service we are interested in. #define REMOTE_CHARACTERISTIC_UUID "6e9b7b27-ca96-4774-b056-8ec5b759fd86"
Some global class objects and variables will be needed for our software.
// Variables HCISharedMemTransportClass HCISharedMemTransport; BLELocalDevice BLEObj(&HCISharedMemTransport); BLELocalDevice& BLE = BLEObj; BLECharacteristic remoteCharacteristic;
We need to add and configure the LCD to be able to observe the results.
// LCD class (Constructor uses STM port numbering) const int rs = PC5, en = PB11, d4 = PB12, d5 = PB13, d6 = PB14, d7 = PB15; LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
In the setup() function we initialise LCD and Bluetooth and start scanning for the device having the service UUID we want to connect.
void setup() { // initialise LCD lcd.begin(16, 2); lcd.clear(); // initialise the BLE hardware BLE.begin(); // start scanning for peripherals int ret = 1; do { ret = BLE.scanForUuid(REMOTE_SERVICE_UUID); if (ret == 0) { BLE.end(); BLE.begin(); } } while(ret == 0); }
In a loop() function we check if a peripheral with UUID we are interested in was discovered. If so we check its name. If the remote device's name is as we expect we print its name, stop scanning, and call the function which reads and displays the value of the characteristic. While the function returns, we restart scanning.
void loop() { // check if a peripheral has been discovered BLEDevice peripheral = BLE.available(); if (peripheral) { if (peripheral.localName() == "SUT STM BLE") { // display remote name lcd.setCursor(0,0); lcd.print(peripheral.localName()); // stop scanning int ret = 1; do { ret = BLE.stopScan(); if (ret == 0) { BLE.end(); BLE.begin(); } } while(ret == 0); // display the characteristic value display_characteristic(peripheral); } // peripheral disconnected, start scanning again int ret = 1; do { ret = BLE.scanForUuid(REMOTE_SERVICE_UUID); if (ret == 0) { BLE.end(); BLE.begin(); } } while(ret == 0); } }
The function reads the characteristic value in some steps:
void display_characteristic(BLEDevice peripheral) { // connect to the peripheral if (peripheral.connect()) { // discover peripheral attributes if (peripheral.discoverAttributes()) { // retrieve the remote characteristic to read remoteCharacteristic = peripheral.characteristic(REMOTE_CHARACTERISTIC_UUID); if (remoteCharacteristic) { // if the peripheral is connected display the value if (peripheral.connected()) { // check if the characteristic can be read if (remoteCharacteristic.canRead()){ char char_text[16]; remoteCharacteristic.readValue(char_text,16); int i=remoteCharacteristic.valueLength(); lcd.setCursor(0,1); lcd.write(char_text,i); } // if (remoteCharacteristic.canRead()) } // if (peripheral.connected()) } // if (remoteCharacteristic) peripheral.disconnect(); } // if (peripheral.discoverAttributes()) } // if (peripheral.connect()) }
You should be able to read the name of the remote device in the first line of the LCD. After establishing the connection, the characteristic value should appear on the display's second line.
What types of data can be sent with the characteristics?: The BLE characteristics can send any value as a single byte or a sequence of bytes. BLE does not interpret the data in characteristic in any way. Interpretation lies on the side of the application.
What if I need to read or write more data than a single text?: It is possible to implement many services in one device and add many characteristics to one service. Theoretically, the maximum number of characteristics in one service is 512, but the implementation and the memory available for the BLE library limit it.
What is the maximum length of the data in the characteristics?: The maximum length of the data in the characteristics in the BLE specification is 512 bytes.
Can I send data between SoCs coming from different vendors?: That's what the specifications of the network protocols are defined for. If devices are in their transmission range (i.e. in one physical laboratory site) you can implement a BLE server on one type of SoC and a BLE client on another.