This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
en:iot-open:practical:hardware:sut:esp32:iot_8 [2024/04/15 17:36] – ktokarz | en:iot-open:practical:hardware:sut:esp32:iot_8 [2025/04/12 08:37] (current) – [IoT8: BLE Communication with characteristics] ktokarz | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== | + | ====== |
- | This scenario presents how to create the Bluetooth Low Energy server device and corresponding client device. The server can be the sensor device which responds to the client with the results of the measurements. This can also be the output device, which we can control writing the data to. The client connects to a server and reads the data. This scenario presents the use of the concept of services and characteristics. | + | This scenario presents how to create the Bluetooth Low Energy server device and the corresponding client device. The server can be the sensor device which responds to the client with the results of the measurements. This can also be the output device, which we can control |
===== Prerequisites ===== | ===== Prerequisites ===== | ||
- | 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 IoT_7 exercise is recommended. | + | 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 [[en: |
===== Suggested Readings and Knowledge Resources ===== | ===== Suggested Readings and Knowledge Resources ===== | ||
Line 16: | Line 16: | ||
==== Task to be implemented ==== | ==== Task to be implemented ==== | ||
**Task 1.** Implement a program that operates as the BLE server which advertises itself and allows us to connect to. | **Task 1.** Implement a program that operates as the BLE server which advertises itself and allows us to connect to. | ||
+ | \\ | ||
**Task 2.** Implement a client device, to read the exemplary data from a server. | **Task 2.** Implement a client device, to read the exemplary data from a server. | ||
==== Start ==== | ==== Start ==== | ||
- | You can use the beacon and simple client programs from IoT_7 as the starting point. | + | You can use the beacon and simple client programs from [[en: |
==== Steps ==== | ==== Steps ==== | ||
- | We will present the lab in a few steps. We begin with a simple program which advertises the device with a single service containing a single characteristic. It allows us to establish a connection with a client and, if successfully connected, responds with simple text data. In further steps, we will improve the program | + | We will present the lab in a few steps. We begin with a simple program which advertises the device with a single service containing a single characteristic. It allows us to establish a connection with a client and, if successfully connected, responds with simple text data. The program |
=== Step 1 === | === Step 1 === | ||
Line 57: | Line 58: | ||
#define CHARACTERISTIC_UUID " | #define CHARACTERISTIC_UUID " | ||
</ | </ | ||
- | The setup() function creates and initialises the BLE device instance with the name "SUT BLE device", | + | |
+ | Before we implement the setup() function, we have to create the callback function executed in case of connection and disconnection events. It allows us to control the advertising process with the deviceConnected flag. | ||
+ | |||
+ | <code c> | ||
+ | class MyServerCallbacks: | ||
+ | void onConnect(BLEServer* pServer) { | ||
+ | deviceConnected = true; | ||
+ | }; | ||
+ | |||
+ | void onDisconnect(BLEServer* pServer) { | ||
+ | deviceConnected = false; | ||
+ | } | ||
+ | }; | ||
+ | </ | ||
+ | |||
+ | The setup() function creates and initialises the BLE device instance with the name "SUT BLE device", | ||
< | < | ||
- | BLEDevice:: | + | // Initialise the BLE device |
- | pServer = BLEDevice:: | + | BLEDevice:: |
+ | |||
+ | // Create | ||
+ | pServer = BLEDevice:: | ||
+ | |||
+ | // Set callback function for handling server events | ||
+ | pServer-> | ||
+ | |||
+ | // Create the service in the server | ||
pService = pServer-> | pService = pServer-> | ||
+ | |||
</ | </ | ||
Data for reading or writing is organised using characteristics. In this example, we create one characteristic with reading and writing enabled, and the initial data as the "BLE onboard" | Data for reading or writing is organised using characteristics. In this example, we create one characteristic with reading and writing enabled, and the initial data as the "BLE onboard" | ||
< | < | ||
+ | // Create the characteristic in the service | ||
pCharacteristic = pService-> | pCharacteristic = pService-> | ||
| | ||
Line 70: | Line 97: | ||
| | ||
); | ); | ||
+ | |||
+ | // Set the initial value of the characteristic. | ||
+ | // We will be able to read it by client | ||
pCharacteristic-> | pCharacteristic-> | ||
</ | </ | ||
In the advertising packet, we can add the service UUID. | In the advertising packet, we can add the service UUID. | ||
< | < | ||
+ | // Get the pointer to the advertising object | ||
pAdvertising = BLEDevice:: | pAdvertising = BLEDevice:: | ||
+ | |||
+ | // Add the service UUID to the advertising | ||
pAdvertising-> | pAdvertising-> | ||
+ | |||
+ | // Enable reading extended information with scan response packet | ||
pAdvertising-> | pAdvertising-> | ||
</ | </ | ||
Line 83: | Line 118: | ||
After preparing all the elements we can close the setup() function by starting the service and beginning advertising. | After preparing all the elements we can close the setup() function by starting the service and beginning advertising. | ||
< | < | ||
+ | // Starting the service and advertising process | ||
pService-> | pService-> | ||
pAdvertising-> | pAdvertising-> | ||
</ | </ | ||
- | The loop function | + | In the loop function, we need to handle |
< | < | ||
void loop(){ | void loop(){ | ||
- | | + | if (!deviceConnected && !advStarted) { |
+ | pServer-> | ||
+ | advStarted = true; | ||
+ | } | ||
+ | |||
+ | if (deviceConnected){ | ||
+ | advStarted = false; | ||
+ | } | ||
+ | | ||
}; | }; | ||
</ | </ | ||
Line 95: | Line 139: | ||
=== Step 2 === | === Step 2 === | ||
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. | 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. | ||
- | {{ en: | + | {{ en: |
=== Step 3 === | === Step 3 === | ||
Line 102: | Line 146: | ||
#include " | #include " | ||
#include " | #include " | ||
+ | #include " | ||
</ | </ | ||
Next, we define the UUIDs for remote service and a characteristic. Notice they must match the ones defined in the server. | Next, we define the UUIDs for remote service and a characteristic. Notice they must match the ones defined in the server. | ||
Line 108: | Line 153: | ||
#define SERVICE_UUID " | #define SERVICE_UUID " | ||
// The characteristic of the remote service we are interested in. | // The characteristic of the remote service we are interested in. | ||
- | #define REMOTE_CHARACTERISTIC_UUID "6e9b7b28-ca96-4774-b056-8ec5b759fd86" | + | #define REMOTE_CHARACTERISTIC_UUID "6e9b7b27-ca96-4774-b056-8ec5b759fd86" |
</ | </ | ||
Line 119: | Line 164: | ||
static BLERemoteCharacteristic* pRemoteCharacteristic; | static BLERemoteCharacteristic* pRemoteCharacteristic; | ||
static BLEAdvertisedDevice* myDevice; | static BLEAdvertisedDevice* myDevice; | ||
+ | </ | ||
+ | We need to add and configure the LCD to be able to observe the results. | ||
+ | <code c> | ||
+ | // LCD display pins and class declaration | ||
+ | #define LCD_RS 2 | ||
+ | #define LCD_ENABLE 1 | ||
+ | #define LCD_D4 39 | ||
+ | #define LCD_D5 40 | ||
+ | #define LCD_D6 41 | ||
+ | #define LCD_D7 42 | ||
+ | static Adafruit_LiquidCrystal lcd(LCD_RS, LCD_ENABLE, LCD_D4, LCD_D5, LCD_D6, LCD_D7); | ||
</ | </ | ||
Line 128: | Line 184: | ||
class MyAdvertisedDeviceCallbacks: | class MyAdvertisedDeviceCallbacks: | ||
void onResult(BLEAdvertisedDevice advertisedDevice) { | void onResult(BLEAdvertisedDevice advertisedDevice) { | ||
+ | // We will print the asterix for every device found | ||
+ | lcd.print(" | ||
+ | | ||
// We have found a device, let's see if it contains the service we are looking for. | // 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))) { | if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(BLEUUID(SERVICE_UUID))) { | ||
+ | // Our device has the service we need. We can stop scanning. | ||
BLEDevice:: | BLEDevice:: | ||
+ | | ||
+ | // Create the instance of remote device | ||
myDevice = new BLEAdvertisedDevice(advertisedDevice); | myDevice = new BLEAdvertisedDevice(advertisedDevice); | ||
+ | | ||
+ | // Pass information to other part of the program to connect to the device | ||
doConnect = true; | doConnect = true; | ||
doScan = true; | doScan = true; | ||
+ | | ||
+ | |||
} // Found our server | } // Found our server | ||
Line 141: | Line 207: | ||
</ | </ | ||
- | The second callback class helps to inform the other parts of the program about the connection close. | + | The second callback class helps to inform the other parts of the program about the connection close. It will additionally inform us about the current state of the program. |
< | < | ||
class MyClientCallback : public BLEClientCallbacks { | class MyClientCallback : public BLEClientCallbacks { | ||
void onConnect(BLEClient* pclient) { | void onConnect(BLEClient* pclient) { | ||
+ | lcd.setCursor(0, | ||
+ | lcd.print(" | ||
} | } | ||
void onDisconnect(BLEClient* pclient) { | void onDisconnect(BLEClient* pclient) { | ||
+ | lcd.setCursor(0, | ||
+ | lcd.print(" | ||
+ | lcd.setCursor(0, | ||
connected = false; | connected = false; | ||
} | } | ||
- | }; | + | };</ |
- | </ | + | |
The setup function initialises the Bluetooth, registers the advertising callback function, and starts the scan to look for nearby devices. | The setup function initialises the Bluetooth, registers the advertising callback function, and starts the scan to look for nearby devices. | ||
< | < | ||
void setup() { | void setup() { | ||
+ | // Initialise the LCD and print the welcome message | ||
+ | lcd.begin(16, | ||
+ | delay(1000); | ||
+ | lcd.setCursor(0, | ||
+ | lcd.print(" | ||
+ | |||
// Initialise the Bluetooth | // Initialise the Bluetooth | ||
BLEDevice:: | BLEDevice:: | ||
Line 169: | Line 245: | ||
pBLEScan-> | pBLEScan-> | ||
pBLEScan-> | pBLEScan-> | ||
+ | | ||
+ | // Print the message on the LCD | ||
+ | lcd.setCursor(0, | ||
+ | lcd.print(" | ||
+ | lcd.setCursor(0, | ||
| | ||
// Start the scan for 30 seconds | // Start the scan for 30 seconds | ||
Line 175: | Line 256: | ||
</ | </ | ||
- | 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 " | + | 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 " |
- | < | + | \\ |
+ | In a loop() function we will periodically read the characteristic value. It is a good practice to check if the characteristic is properly retrieved and readable before reading the value. | ||
+ | < | ||
void loop() { | void loop() { | ||
if (doConnect == true) { | if (doConnect == true) { | ||
- | connectToServer(); | + | // Establish the connection to the server |
+ | connectToServer(); | ||
doConnect = false; | doConnect = false; | ||
} | } | ||
Line 185: | Line 269: | ||
if (!connected) { | if (!connected) { | ||
if(doScan){ | if(doScan){ | ||
- | BLEDevice:: | + | |
+ | | ||
} | } | ||
+ | } | ||
+ | | ||
+ | if (connected) { | ||
+ | lcd.setCursor(0, | ||
+ | | ||
+ | // Is the characteristic properly retrieved? | ||
+ | if(pRemoteCharacteristic != nullptr) | ||
+ | | ||
+ | // Is the characteristic readable? | ||
+ | if(pRemoteCharacteristic-> | ||
+ | | ||
+ | // We can safely read the value | ||
+ | lcd.print(pRemoteCharacteristic-> | ||
} | } | ||
delay(1000); | delay(1000); | ||
Line 220: | Line 318: | ||
</ | </ | ||
- | === Step 4 === | ||
- | BLE peripheral device stops advertising after establishing a connection. While the connection is closed or lost it should restart advertising to be able to accept further connections. Doing it is possible with the use of callback functions, executed in selected events occurrence. We need to add the callback functions called on the connection and disconnection events. We will use two global boolean variables, one to store the state of the device, and another to control the single advertising start. | ||
- | <code c> | ||
- | bool deviceConnected = false; | ||
- | bool advStarted = true; //first advertise starts automatically | ||
- | |||
- | class MyServerCallbacks: | ||
- | void onConnect(BLEServer* pServer) { | ||
- | deviceConnected = true; | ||
- | }; | ||
- | |||
- | void onDisconnect(BLEServer* pServer) { | ||
- | deviceConnected = false; | ||
- | } | ||
- | }; | ||
- | </ | ||
- | In the setup() function, just after the creation of the server we set up the callbacks. | ||
- | < | ||
- | pServer-> | ||
- | </ | ||
- | In the loop() function we have to program the starting of the advertising while the connection is ended. The startAdvertising() function should be called just once so the advStarted boolean variable helps us to control this process. | ||
- | < | ||
- | void loop(){ | ||
- | if (!deviceConnected && !advStarted) { | ||
- | pServer-> | ||
- | advStarted = true; | ||
- | } | ||
- | |||
- | if (deviceConnected){ | ||
- | advStarted = false; | ||
- | } | ||
- | } | ||
- | delay(500); | ||
- | }; | ||
- | </ | ||
==== Result validation ==== | ==== Result validation ==== | ||
- | You should be able to find the device in the BLE neighbourhood. It should allow us to establish | + | You should be able to observe messages describing |
===== FAQ ===== | ===== FAQ ===== | ||