This scenario presents the Bluetooth Low Energy client device with a notification/indication mechanism for receiving data. It works in cooperation with the server device created in the IoT9: BLE Communication with notify/indicate.
It is necessary to understand the principles of the Bluetooth Low Energy protocol with concepts of services, characteristics and descriptors. Notification and indication methods of data transmission should be known. We will use in this scenario the knowledge and the server device created in IoT_9.
This scenario is intended to be implemented using actual equipment on-site. Although it is possible to make it remotely, with the use of two BLE laboratory nodes, to observe all the details you need to have your mobile phone or similar device with an application which allows you to scan and explore your Bluetooth Low Energy devices and communicate with them. One of the best applications is nRF Connect created by Nordic Semiconductors.
Implement a program that operates as the BLE client which allows us to read the data from the server with a notification/indication mechanism.
A similar program is available as the software example for the BLE client device in the ESP32 BLE Arduino package. You can use this example, or create the program from scratch based on the info from the following steps.
We will pass through the lab in a few steps. We will create the client device with the definition of remote service and a characteristic. We will subscribe to this characteristic with the notify/indicate feature. With this feature enabled the peripheral device will periodically transmit the data to our client.
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 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.
We start implementation with the libraries needed.
#include "Arduino.h" #include "BLEDevice.h"
Next, we define the UUIDs for remote service and characteristic. Notice they must match the ones defined in the server.
// The remote service we wish to connect to. #define SERVICE_UUID "6e9b7b26-ca96-4774-b056-8ec5b759fd86" // The characteristic of the remote service we are interested in. #define REMOTE_CHARACTERISTIC_UUID "6e9b7b28-ca96-4774-b056-8ec5b759fd86"
Some global variables will be needed for our software.
// Variables static boolean doConnect = false; static boolean connected = false; static boolean doScan = false; static BLERemoteCharacteristic* pRemoteCharacteristic; static BLEAdvertisedDevice* myDevice;
Three callback functions will be defined. The first callback is for advertising.
// Scan for BLE servers and find the first one that advertises // the service we are looking for. class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks { void onResult(BLEAdvertisedDevice advertisedDevice) { // We have found a device, let's see if it contains the service we are looking for. if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(BLEUUID(SERVICE_UUID))) { BLEDevice::getScan()->stop(); myDevice = new BLEAdvertisedDevice(advertisedDevice); doConnect = true; doScan = true; } // Found our server } // onResult }; // MyAdvertisedDeviceCallbacks
Another callback will be executed on the notify event. In the example, we simply print incoming data to the console, but you can use the data in any other way.
static void notifyCallback( BLERemoteCharacteristic* pBLERemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify) { Serial.print("Notify data: "); Serial.write(pData, length); Serial.println(); }
The third callback class helps to inform the other parts of the program about the connection close.
class MyClientCallback : public BLEClientCallbacks { void onConnect(BLEClient* pclient) { } void onDisconnect(BLEClient* pclient) { connected = false; } };
The setup function initialises the Bluetooth, registers the advertising callback function, and starts the scan to look for nearby devices.
void setup() { Serial.begin(115200); Serial.println("BLE Client app"); // Initialise the Bluetooth BLEDevice::init(""); // Retrieve the pointer to the scan module BLEScan* pBLEScan = BLEDevice::getScan(); // Register callback for incoming advertising pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks()); // Start scan parameters with active scan mode pBLEScan->setInterval(1349); pBLEScan->setWindow(449); pBLEScan->setActiveScan(true); // Start the scan for 30 seconds pBLEScan->start(30, false); }
In the loop function, we wait for the information that the server with the service UUID we were interested in was found. It is signalled with the use of “doConnect” flag. If it's true we can connect to this server. Once we are connected the “connected” flag is set to be true in the connectToServer() function. We will show this function in a while.
void loop() { if (doConnect == true) { connectToServer(); doConnect = false; } if (!connected) { if(doScan){ BLEDevice::getScan()->start(0); // this is just example to start scan after disconnect, most likely there is better way to do it in arduino } delay(1000); // Delay a second between loops. }
The connection to the server is executed in some steps:
bool connectToServer() { BLEClient* pClient = BLEDevice::createClient(); pClient->setClientCallbacks(new MyClientCallback()); // Connect to the remote BLE Server. pClient->connect(myDevice); pClient->setMTU(517); // Obtain a reference to the service we are after in the remote BLE server. BLERemoteService* pRemoteService = pClient->getService(BLEUUID(SERVICE_UUID)); // Obtain a reference to the characteristic in the service of the remote BLE server. pRemoteCharacteristic = pRemoteService->getCharacteristic(BLEUUID(REMOTE_CHARACTERISTIC_UUID)); if(pRemoteCharacteristic->canNotify()) pRemoteCharacteristic->registerForNotify(notifyCallback); connected = true; return true; }
You should be able to find the server device in the BLE neighbourhood, connect to it, subscribe to the notification and receive the incoming data. We should be able to observe periodic increments of the value received with data packets from the server.
Can we connect to the same peripheral with more than one central device?: No, it's not possible. The BLE standard allows only one connection for the peripheral device. It was defined to ensure that BLE will work on constrained devices with small memory.
May I use the data in another way than printing it on the console?: Of course. You can for example change the LED colour on the central device depending on the temperature readings from the peripheral device.