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_7 [2024/03/30 12:50] – [IoT7: Setting-up BLE Beacon] ktokarz | en:iot-open:practical:hardware:sut:esp32:iot_7 [2024/05/02 11:11] (current) – [IOT7: BLE Beacon] ktokarz | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== | + | ====== |
- | This scenario presents how to create the Bluetooth Low Energy beacon device, which broadcasts | + | This scenario presents how to create the Bluetooth Low Energy beacon device which periodically |
< | < | ||
BLE Beacons just broadcast the information, | BLE Beacons just broadcast the information, | ||
+ | </ | ||
+ | <note important> | ||
+ | To do this scenario two stations are needed, one for the beacon and one for the client device. | ||
</ | </ | ||
===== Prerequisites ===== | ===== Prerequisites ===== | ||
Line 11: | Line 15: | ||
* [[en: | * [[en: | ||
* [[en: | * [[en: | ||
- | \\ | + | |
- | To be added here the Eddystone form Advanced Book | + | * Interesting article on Beacons with a description of Eddystone, and iBeacon packets((https:// |
- | \\ | + | |
- | To be added here BLE form Advanced Book | + | |
- | * Interesting article on Beacons with description of Eddystone, and iBeacon packets((https:// | + | |
===== Hands-on Lab Scenario ===== | ===== Hands-on Lab Scenario ===== | ||
- | 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, | + | This scenario is intended to be implemented using two laboratory nodes. One node will work as the beacon device, while the second will receive the beacon' |
==== Task to be implemented ==== | ==== Task to be implemented ==== | ||
- | Implement a program that operates as the BLE beacon which advertises itself with the link to the SUT website. This can be done with the Eddystone beacon format. | + | **Task 1** Implement a program that operates as the BLE beacon which advertises itself with the link to the website. This can be done with the Eddystone beacon format. |
+ | **Task 2** Implement the receiver of advertising frames broadcasted by the beacon device capable of displaying information on the LCD screen. | ||
==== Start ==== | ==== Start ==== | ||
- | The example | + | We need to implement both parts of the software |
- | + | ||
==== Steps ==== | ==== Steps ==== | ||
- | We can go through the lab in a few steps. In the beginning, we will write a simple program which advertises the device with the default parameters set in the software package. After a successful first attempt, we will add our information to the advertising frame, and lastly, we will use the Eddystone format to send the chosen URL address. | + | We can go through the lab in a few steps. In the beginning, we will write a simple program which advertises the device with the default parameters set in the BLE library. As the payload of the advertising frame, we will use the Eddystone format to send the chosen URL address. |
=== Step 1 === | === Step 1 === | ||
- | Let's begin with a very simple program using default advertising. The code should start with including Arduino and BLE libraries. | + | Let's begin with including Arduino and BLE libraries. |
<code c> | <code c> | ||
- | # | + | # |
#include " | #include " | ||
#include " | #include " | ||
</ | </ | ||
We need one variable only which holds the pointer to the advertising class. | We need one variable only which holds the pointer to the advertising class. | ||
- | < | + | < |
BLEAdvertising *pAdvertising; | BLEAdvertising *pAdvertising; | ||
</ | </ | ||
- | The setup function creates and initialises the BLE device instance with a default | + | The setup function creates and initialises the BLE device instance with a chosen |
- | < | + | < |
void setup(){ | void setup(){ | ||
- | BLEDevice:: | + | BLEDevice:: |
pAdvertising = BLEDevice:: | pAdvertising = BLEDevice:: | ||
+ | |||
+ | ... // We will implement this part in step 2 | ||
+ | |||
pAdvertising-> | pAdvertising-> | ||
}; | }; | ||
</ | </ | ||
- | Note that the loop function can remain empty. | + | |
- | < | + | The loop function can remain empty or call the delay() function. |
+ | < | ||
void loop(){ | void loop(){ | ||
+ | | ||
}; | }; | ||
</ | </ | ||
=== Step 2 === | === Step 2 === | ||
- | Let us now introduce some of our information. Sure, if you create the device you would like it to present with the name of your choice. If we add our text in the BLEDevice:: | + | The advertisement frame is composed of fields. Every field starts with its length (1 byte, including the field type byte). The second byte of the field encodes the field type. The selected field codes used in the default advertising frame defined in the ESP32 BLE library are presented in the table below. |
- | <code c> | + | |
- | BLEDevice:: | + | |
- | </ | + | |
- | The advertisement frame is composed of fields. Every field starts with its length (1 byte). The second byte of the field encodes the field type. The selected field codes used in the default advertising frame defined in the ESP32 BLE library are presented in the table below. | + | |
^ Field length ^ Field code ^ Field type ^ Default value ^ | ^ Field length ^ Field code ^ Field type ^ Default value ^ | ||
| 0x02 | 0x01 | Flags | 0x06 | | | 0x02 | 0x01 | Flags | 0x06 | | ||
Line 66: | Line 67: | ||
| 0x02 | 0x0A | Tx Power Level | 0x09 | | | 0x02 | 0x0A | Tx Power Level | 0x09 | | ||
- | === Step 3 === | + | Except for the text information as the device name, it is possible to send other data such as the web address which directs us to additional information. |
- | Except for the text information as the device name, it is possible to send the web address which directs us to additional information. | + | ^ Field length |
- | ^ Field length | + | |
| 3 | 0x03 | Complete list of 16-bit service UUIDs | 0xAAFE | | 3 | 0x03 | Complete list of 16-bit service UUIDs | 0xAAFE | ||
| 14 | 0x16 | Service data | Eddystone format | | 14 | 0x16 | Service data | Eddystone format | ||
- | The Eddystone field starts with service UUID (0xAAFE), next specifies the content type (0x10 for URL), then the transmitting power in [dBm], and then the compressed URL. To compress URLs some default prefixes and suffixes were defined as non-ASCII characters. They are presented in the tables below. | + | The Eddystone field starts with service UUID (0xAAFE), next specifies the content type (0x10 for URL), then the transmitting power in [dBm], and then the compressed URL. |
+ | < | ||
+ | The UUID of Eddystone appears twice. Once as the element of the list of available services, the second time as the identifier of the service field. | ||
+ | </ | ||
+ | To compress URLs some default prefixes and suffixes were defined as non-ASCII characters. They are presented in the tables below. | ||
^ Decimal ^ Hex ^ Prefix ^ | ^ Decimal ^ Hex ^ Prefix ^ | ||
Line 97: | Line 101: | ||
<code c> | <code c> | ||
BLEAdvertisementData oAdvertisementData = BLEAdvertisementData(); | BLEAdvertisementData oAdvertisementData = BLEAdvertisementData(); | ||
- | std::string eddystone_content (" | + | std::string eddystone_content (" |
</ | </ | ||
- | In the code, we need to fill in the empty spaces | + | In the setup() function, we need to fill in the empty spaces |
- | < | + | < |
- | eddystone_content[0] | + | eddystone_content[0] |
eddystone_content[1] | eddystone_content[1] | ||
eddystone_content[2] | eddystone_content[2] | ||
- | eddystone_content[3] | + | eddystone_content[3] |
eddystone_content[4] | eddystone_content[4] | ||
eddystone_content[5] | eddystone_content[5] | ||
eddystone_content[6] | eddystone_content[6] | ||
- | eddystone_content[7] | + | eddystone_content[7] |
eddystone_content[8] | eddystone_content[8] | ||
eddystone_content[9] | eddystone_content[9] | ||
eddystone_content[10] = 0xFE; | eddystone_content[10] = 0xFE; | ||
eddystone_content[11] = 0x10; //URL Eddystone frame type | eddystone_content[11] = 0x10; //URL Eddystone frame type | ||
- | eddystone_content[12] = 0xF4; //Tx power [dBm] (-12dBm) | + | eddystone_content[12] = 0xF4; //Tx power [dBm] |
eddystone_content[13] = 0x00; // | eddystone_content[13] = 0x00; // | ||
+ | eddystone_content[14] = ' | ||
+ | eddystone_content[15] = 0x07; // | ||
</ | </ | ||
+ | < | ||
+ | We created a very short name for the URL to fit in one line of LCD screen (16 characters). The resulting text would be " | ||
+ | </ | ||
The created payload can be used as the payload data of the advertising packet. | The created payload can be used as the payload data of the advertising packet. | ||
- | < | + | < |
oAdvertisementData.addData(eddystone_content); | oAdvertisementData.addData(eddystone_content); | ||
pAdvertising-> | pAdvertising-> | ||
+ | </ | ||
+ | |||
+ | === Step 3 === | ||
+ | It is the time to receive the advertising packet. We will do it with another device starting with including the libraries required. | ||
+ | <code c> | ||
+ | #include " | ||
+ | #include " | ||
+ | #include " | ||
+ | </ | ||
+ | There can be a lot of BLE devices in the range of our receiver. We should find the one that we are interested in so we define the UUID of the Eddystone service. | ||
+ | <code c> | ||
+ | #define SERVICE_UUID " | ||
+ | </ | ||
+ | |||
+ | Our program will use objects of two classes and some variables: | ||
+ | <code c> | ||
+ | static BLEAdvertisedDevice* myDevice; // Class for remote BLE device | ||
+ | BLEScan* pBLEScan; | ||
+ | |||
+ | uint8_t * advPayload; | ||
+ | char eddystoneUrl[20]; | ||
+ | String advName; | ||
+ | int advLength; | ||
+ | int advIndex; | ||
+ | int advEddystoneLength; | ||
+ | int i; | ||
+ | </ | ||
+ | |||
+ | The received information will be displayed on the LCD, so we need to configure it (similar to the scenario EMB5) | ||
+ | <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); | ||
+ | </ | ||
+ | |||
+ | The setup function initialises the display and the Bluetooth, sets up and starts the scanning process. An important step is registering the callback function. The callback function is called every time the scanner receives the advertising frame. | ||
+ | <code c> | ||
+ | void setup() { | ||
+ | // Initialise LCD | ||
+ | lcd.begin(16, | ||
+ | delay(1000); | ||
+ | lcd.print(" | ||
+ | | ||
+ | // Initialise the Bluetooth | ||
+ | BLEDevice:: | ||
+ | | ||
+ | // Retrieve the pointer to the scan module | ||
+ | pBLEScan = BLEDevice:: | ||
+ | | ||
+ | // Register callback for incoming advertising | ||
+ | pBLEScan-> | ||
+ | | ||
+ | // Start scan parameters with active scan mode | ||
+ | pBLEScan-> | ||
+ | pBLEScan-> | ||
+ | pBLEScan-> | ||
+ | | ||
+ | // Start the scan for 5 seconds | ||
+ | pBLEScan-> | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | In the loop function, we restart scanning every 5 seconds with 1 second delay. | ||
+ | < | ||
+ | The start function of the BLEScan class is blocking, so it stops program execution for the time of scanning. | ||
+ | </ | ||
+ | <code c> | ||
+ | void loop() { | ||
+ | delay(1000); | ||
+ | // Repeat scanning | ||
+ | pBLEScan-> | ||
+ | } | ||
</ | </ | ||
=== Step 4 === | === Step 4 === | ||
- | The advertisement frame is quite short so normally additional information is sent upon Scan Request with Scan Response frame. It can contain | + | The callback function checks if the device which sent the advertising frame implements the Eddystone service. This is how we filter out all other BLE devices available in the range. |
- | < | + | < |
+ | if we have more than one Eddystone device nearby we would have to implement additional filtering. | ||
+ | </ | ||
+ | |||
+ | After a successful search for a device with Eddystone service, we can display its name and proceed with displaying the content of the Eddystone field. Because the code is quite complex we present the whole callback function below. Notice that we decode chosen compressed fields only (prefix = 0x00, suffice = 0x07). You would need to decode other values for compatibility with the Eddystone format. | ||
+ | <code c> | ||
+ | class MyAdvertisedDeviceCallbacks: | ||
+ | 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))) { | ||
+ | |||
+ | // Display the name of the remote device | ||
+ | lcd.setCursor(0, | ||
+ | lcd.print(advertisedDevice.getName().c_str()); | ||
+ | |||
+ | // Get the length of the payload and the pointer to it | ||
+ | advLength = advertisedDevice.getPayloadLength(); | ||
+ | advPayload = advertisedDevice.getPayload(); | ||
+ | |||
+ | // Search for the Eddystone field (field ID = 0x16) | ||
+ | advIndex = 0; | ||
+ | while (advIndex< | ||
+ | { | ||
+ | if (advPayload[advIndex+1]==0x16) { | ||
+ | |||
+ | // Eddystone field found, get the length of it | ||
+ | advEddystoneLength = advPayload[advIndex]; | ||
+ | |||
+ | // Display the Eddystone field | ||
+ | lcd.setCursor(0, | ||
+ | |||
+ | // Prefix decoding | ||
+ | if (advPayload[advIndex+6]==0x00) { | ||
+ | lcd.print(" | ||
+ | } | ||
+ | |||
+ | // ULR name | ||
+ | for(i=0; i< | ||
+ | eddystoneUrl[i]=(char)advPayload[advIndex+7+i]; | ||
+ | } | ||
+ | eddystoneUrl[i]=(char)NULL; | ||
+ | lcd.print(eddystoneUrl); | ||
+ | |||
+ | // Suffix decoding | ||
+ | if (advPayload[advIndex+advEddystoneLength]==0x07){ | ||
+ | lcd.print(" | ||
+ | } | ||
+ | } // Eddystone field found | ||
+ | advIndex ++; | ||
+ | } // Search for Eddystone | ||
+ | } // Found our server | ||
+ | } // onResult | ||
+ | }; // MyAdvertisedDeviceCallbacks | ||
+ | </ | ||
+ | Additional filtering of remote devices can be done with their names. We can add in the " | ||
+ | <code c> | ||
+ | String name; | ||
+ | name = advertisedDevice.getName().c_str(); | ||
+ | if (name.equals(" | ||
+ | { | ||
+ | // here the internal code of the MyAdvertisedDeviceCallbacks() | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | === Step 5 === | ||
+ | Let's return to the beacon device. | ||
+ | < | ||
oScanResponseData.setName(" | oScanResponseData.setName(" | ||
pAdvertising-> | pAdvertising-> | ||
</ | </ | ||
- | < | + | |
- | If the text used as an advertisement (or scan response) name is too long the BLE library automatically empties the text. If the device name is too long it is replaced with the " | + | |
- | </ | + | |
==== Result validation ==== | ==== Result validation ==== | ||
- | You should be able to observe | + | After the implementation of steps 1-4, you should be able to see the name of the beacon device |
==== Further work ==== | ==== Further work ==== | ||
- | You can try the example software for the beacon device which is available in the examples at the path: | + | You can try to implement the beacon device compatible with iBeacon. The description can be found on the website ((https:// |
- | < | + | |
- | C: | + | |
- | </ | + | |
- | It is more complex but enables automatic determination of packet length depending on the URL provided. | + | |
- | \\ | + | |
- | You can also try to implement the beacon device compatible with iBeacon. The description can be found on the website ((https:// | + | |
===== FAQ ===== | ===== FAQ ===== | ||
- | **What is the maximum length of the advertising frame?**: It depends on the BLE version. | + | **What is the maximum length of the advertising frame?**: It depends on the BLE version. |
\\ | \\ | ||
**How often should the device send advertising frames?**: The delay between advertising frames can vary depending on the device. Beacon devices should send them every 100-200 ms. Other devices usually advertise every 2 seconds. Notice that advertising consumes energy, so the battery-powered devices advertise with lower frequency. | **How often should the device send advertising frames?**: The delay between advertising frames can vary depending on the device. Beacon devices should send them every 100-200 ms. Other devices usually advertise every 2 seconds. Notice that advertising consumes energy, so the battery-powered devices advertise with lower frequency. |