IoT7: BLE Beacon

This scenario presents how to create the Bluetooth Low Energy beacon device which periodically broadcasts a small amount of information, and the client device which can receive packets sent by the beacon. Beacons are usually used for sending useful information (eg. the web address of the owner, a link to the page with tourist information). In some cases, they simply send the identification number recognised by a dedicated mobile application allowing the users to localise themselves.

BLE Beacons just broadcast the information, they are not capable of receiving any information from the users nearby.
To do this scenario two stations are needed, one for the beacon and one for the client device.


We will use the advertising process in this scenario so an understanding of the principles of the Bluetooth Low Energy protocol is necessary. It's good to understand the Eddystone data format used to encode URLs in advertising frames.

Suggested Readings and Knowledge Resources

  • Interesting article on Beacons with a description of Eddystone, and iBeacon packets[1]

Hands-on Lab Scenario

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's packets and display information on the LCD.

Task to be implemented

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.


We need to implement both parts of the software to be able to observe the results (steps 1-4). The Task 1 we implement in steps 1 and 2. The Task 2 we implement in steps 3 and 4. At the end, we return to Task 1 with step 5.


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

Let's begin with including Arduino and BLE libraries.

#include "Arduino.h"
#include "BLEDevice.h"
#include "BLEAdvertising.h"

We need one variable only which holds the pointer to the advertising class.

BLEAdvertising *pAdvertising;

The setup function creates and initialises the BLE device instance with a chosen name. If we add our text in the BLEDevice::init() function we will see this text as the device name in the advertising frame. It will also appear as the value of the Device Name characteristic in the Generic Access service. If we don't define the name it will use the default “ESP32”. While the device is ready we can get the pointer to its class using it to configure the advertising packet. After configuration, we can start the advertising process.

void setup(){
BLEDevice::init("SUT BLE device");
pAdvertising = BLEDevice::getAdvertising();
... // We will implement this part in step 2

The loop function can remain empty or call the delay() function.

void loop(){

Step 2

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.

Field length Field code Field type Default value
0x02 0x01 Flags 0x06
0x05 0x12 Peripheral Connection Interval Range 0x20004000
0x06 0x09 Complete Local Name “ESP32”
0x02 0x0A Tx Power Level 0x09

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. There are some formats defined for use in beacon devices. One of them is Eddystone invented by Google. It is used in the advertisement frames for simplification of the URL encoding. It requires the presence of the field “List of 16-bit UUIDs” (code 0x03) which shows the UUIDs of the Eddystobe service (0xAAFE value), and the service data field (code 0x16) compatible with Eddystone format. These additional fields are shown in the table below.

Field length Field code Field type Value
3 0x03 Complete list of 16-bit service UUIDs 0xAAFE
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.

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
0 0x00 http://www.
1 0x01 https://www.
2 0x02 http://
3 0x03 https://
Decimal Hex Suffix
0 0x00 .com/
1 0x01 .org/
2 0x02 .edu/
3 0x03 .net/
4 0x04 .info/
5 0x05 .biz/
6 0x06 .gov/
7 0x07 .com
8 0x08 .org
9 0x09 .edu
10 0x0a .net
11 0x0b .info
12 0x0c .biz
13 0x0d .gov

The Eddystone payload can be created as the std::string class object as required by the function addData() from the BLEAdvertisementData class. So first we need to declare the BLEAdvertismentData class object, and string object with the placeholder for the payload.

BLEAdvertisementData oAdvertisementData = BLEAdvertisementData();
std::string eddystone_content ("                "); //16 spaces for advertising packet

In the setup() function, we need to fill in the empty spaces with details of the advertising packet.

eddystone_content[0]  = 0x02;   //Length of FLags field
eddystone_content[1]  = 0x01;   //Flags ID
eddystone_content[2]  = 0x06;   //Flags value
eddystone_content[3]  = 0x03;   //Length of service field
eddystone_content[4]  = 0x03;   //List of 16-bit UUIDs
eddystone_content[5]  = 0xAA;   //UUID of Google Eddystone
eddystone_content[6]  = 0xFE;
eddystone_content[7]  = 8;      //length of Eddystone field
eddystone_content[8]  = 0x16;   //Service data
eddystone_content[9]  = 0xAA;   //Eddystone UUID
eddystone_content[10] = 0xFE;
eddystone_content[11] = 0x10;   //URL Eddystone frame type
eddystone_content[12] = 0xF4;   //Tx power [dBm]
eddystone_content[13] = 0x00;   //prefix: "http://www."
eddystone_content[14] = '2';    //URL
eddystone_content[15] = 0x07;   //suffix: ".com"
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.


Step 3

It is the time to receive the advertising packet. We will do it with another device starting with including the libraries required.

#include "Arduino.h"
#include "BLEDevice.h"
#include "Adafruit_LiquidCrystal.h"

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.

#define SERVICE_UUID "feaa"

Our program will use objects of two classes and some variables:

static BLEAdvertisedDevice* myDevice; // Class for remote BLE device
BLEScan* pBLEScan;                    // Class for local scanner device
uint8_t * advPayload;     // Pointer to the payload of incoming adv packet
char eddystoneUrl[20];    // Buffer for text to display
String advName;           // Remote device name
int advLength;            // Length of payload of adv. packet
int advIndex;             // Index of the byte we proceed
int advEddystoneLength;   // Length of the Eddystone field
int i;

The received information will be displayed on the LCD, so we need to configure it (similar to the scenario EMB5)

// 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.

void setup() {
  // Initialise LCD
  lcd.begin(16, 2);
  lcd.print("BLE CLient");
  // Initialise the Bluetooth
  // Retrieve the pointer to the scan module
  pBLEScan = BLEDevice::getScan();
  // Register callback for incoming advertising
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  // Start scan parameters with active scan mode
  // Start the scan for 5 seconds
  pBLEScan->start(5, false);

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.
void loop() {
  // Repeat scanning

Step 4

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.

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))) {
      // Display the name of the remote device
      // 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<=advLength)
        if (advPayload[advIndex+1]==0x16) {
          // Eddystone field found, get the length of it
          advEddystoneLength = advPayload[advIndex];
          // Display the Eddystone field
          // Prefix decoding
          if (advPayload[advIndex+6]==0x00) {
          // ULR name
          for(i=0; i<advEddystoneLength-7; i++){
          eddystoneUrl[i]=(char)NULL; // Terminate the string
          // Suffix decoding
          if (advPayload[advIndex+advEddystoneLength]==0x07){
        } // 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 “MyAdvertisedDeviceCallbacks()” function the comparison of the remote device name with the constant string. It should be done if the Eddystone service was found.

String name;
name = advertisedDevice.getName().c_str();
if (name.equals("SUT BLE device"))
    // here the internal code of the MyAdvertisedDeviceCallbacks()

Step 5

Let's return to the beacon device. The advertisement frame is quite short so normally additional information is sent upon Scan Request with Scan Response frame. It can contain the device name which can differ from the name we specified in step 1 (in the case of normal operation we can read it as the value of the Generic Access Service). To specify different names in advertising frame and service characteristics we can use the setScanResponseData() function from BLEAdvertising class.

oScanResponseData.setName("SUT Beacon");

Result validation

After the implementation of steps 1-4, you should be able to see the name of the beacon device in the first line of LCD and the URL from the Eddystone field in the second line of LCD. Implementation of step 5 should result in a change of the device name.

Further work

You can try to implement the beacon device compatible with iBeacon. The description can be found on the website [2].


What is the maximum length of the advertising frame?: It depends on the BLE version. In previous versions, it used to be 31 bytes only, but in newer versions, even 255 bytes are allowed.
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.
What if I need to send some info other than in Eddystone or iBeacon format?: You can send the text or even some data as part of the advertising frame. Some devices use the advertising frames to send simple 1-2 bytes of measurement data. More data can be sent after establishing the connection between the primary and secondary devices with the use of characteristics.
What is the purpose of the transmitting power data presence in the advertising frame?: The value sent as the transmitting power should represent real power at a 0-meter distance from the beacon. It can be used to estimate the actual distance of the user from the beacon. The distance needs to be calculated by application on the mobile phone with knowledge of the transmitting power of the beacon and actual measurement of received signal strength (known as RSSI).

Project information

This Intellectual Output was implemented under the Erasmus+ KA2.
Project IOT-OPEN.EU Reloaded – Education-based strengthening of the European universities, companies and labour force in the global IoT market.
Project number: 2022-1-PL01-KA220-HED-000085090.

Erasmus+ Disclaimer
This project has been funded with support from the European Commission.
This publication reflects the views of only the author, and the Commission cannot be held responsible for any use that may be made of the information contained therein.

Copyright Notice
This content was created by the IOT-OPEN.EU Reloaded consortium, 2022,2024.
The content is Copyrighted and distributed under CC BY-NC Creative Commons Licence, free for Non-Commercial use.

en/iot-open/practical/hardware/sut/esp32/iot_7.txt · Last modified: 2024/05/02 11:11 by ktokarz
CC Attribution-Share Alike 4.0 International Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0