ESP32 (32) – BLE, iBeacon

In my previous article I explained the Bluetooth Low Energy technology and the advertising process.

You learned that a BLE device can leverage the advertising packets to send data; in this case the device is called broadcaster and the devices which receive data are called observers.

The payload of an advertising packet has the following structure:


ADV ADDR is the device MAC address (this is the address displayed by the program developed in the previous article) and ADV DATA is a field, with a max length of 31 bytes, that contains one or more structures, each with 3 elements:

  • AD length is the total length (in bytes) of each data structure
  • AD type is the type of data contained in the structure
  • AD data is the real data

The official website of the Bluetooth Special Interest Group lists all the available AD types.

A device, for example, can transmit its local name using the AD type 0x09:


In scan mode, the Bluetooth driver returns to the program the received data (ADV DATA) in the scan_result->scan_rst.ble_adv array. This array contains uint8_t values and it’s size is scan_result->scan_rst.adv_data_len.

The Bluedroid library contains a method, esp_ble_resolve_adv_data(), which allows to get the value for a specific AD type passing the raw data. The header file esp_gap_ble_api.h contains definitions for the most common AD types:


In my Github repository you can find an updated version of the scan program. Thanks to what explained above, now the program can also display – if available – the name of the device:



A particular family of broadcaster devices are the iBeacons. These devices have been designed by Apple to allow interaction with IOS devices (iPhone …) based on location awareness. Let’s make an example: an iPhone can “notice” that it is close to a particular iBeacon, associated with a room in a museum, and therefore offer the user a brief guide to the exhibited works.


iBeacon specifications are available on Apple’s developer portal. iBeacons work transmitting advertising packets with  specific payload (ADV DATA):


The first structure has AD type = flags (0x01). Each bit has a different meaning, usually iBeacons use 0x0A value for AD data.

The second structure has type = 0xFF, that is Manufacturer Specific Data. The Bluetooth standard allows the different manufacturers to use this ID to transmit custom data. The total data length is 25 bytes (0x1A – 0x01 that is the length of the AD type field).

Apple specifications further subdivide the AD data field in several elements:


The first field is the manufacturer/company; iBeacons normally use the code 0x004C, assigned to Apple Inc. The next two fields define the iBeacon type and have a fixed value (0x02 e 0x15). The UUID field, together with the Major and Minor ones (optional, they can have a value of 0) uniquely identifies each iBeacon.  (insieme con i campi Major e Minor (facoltativi, possono essere impostati a 0) identificano univocamente il singolo iBeacon. Finally, the TX power field contains a measurement, one meter away from the iBeacon, of the received power and is useful for  precisely estimate the distance between the phone and the iBeacon itself.


I developed a program for the esp32 chip which turns a relay on if it detects a specific iBeacon. Via menuconfig you can configure the UUID of the iBeacon which triggers the led, the pin the led is connected to and the timeout – in seconds – after which the program turns the led off if the iBeacon is not detected anymore. You can moreover set a power threshold to control the distance at which the iBeacon is detected.

To parse the received packet and get the UUID value, in my program I used the method described in this article (parsing using a struct).

The program verifies if the received packet (event ESP_GAP_SEARCH_INQ_RES_EVT) was sent by an iBeacon checking that the packet length is 30 bytes and that its header contains the values listed above:

// iBeacon fixed header
ibeacon_header_t ibeacon_fixed_header = {
  .flags = {0x02, 0x01, 0x06},
  .length = 0x1A,
  .type = 0xFF,
  .company_id = 0x004C,
  .beacon_type = 0x1502

It compares the fixed header with the received one using memcmp, function that compares two blocks in memory:

if(memcmp(adv_data, ibeacon_fixed_header, sizeof(ibeacon_fixed_header)))
  result = true;

The source program is available in my Github repository, here’s a video that shows how it works:

ESP32 (31) – BLE, GAP

In my previous tutorials you learned how to use the wifi interface of the esp32 chip. Starting from this post, I’m going to explain you the second wireless technology the esp32 chip supports: bluetooth.

In particular, my tutorial will be about Bluetooth Low Energy (BLE), sometimes called also Bluetooth 4.0 or Bluetooth Smart:


Bluetooth Low Energy

BLE is a technology to build personal wireless area networks (WPAN); that is it allows to put in communication different devices (computers, smartphones, smartwatches…) “close” to each other (a theoretical maximum distance of 100m). As the name suggests, version 4.0 of the Bluetooth standard was designed to reduce the power consumption of the devices connected to the network.

Devices are divided into two families:

  • central
  • peripheral

the first ones (central) are devices like PCs, tablets or smartphones with good processing power and memory. The second ones (peripheral) are instead sensors, tags… with less hardware resources and power. A central device can be connected to more peripheral devices at the same time, while it’s not true the opposite:


BLE devices periodically report their presence by transmitting advertising packets. The advertising packet can contain up to 31 bytes of data and the transmission frequency can be chosen by the single device: reducing this frequency can indeed reduce energy consumption.

If a BLE device, after having received an avertising package, wants to obtain more information from the device that transmitted it, it can request a second packet of information (always for a maximum of 31 bytes), the scan response packet. The transmission of this second data package is optional:


A BLE device can take advantage of advertising packages to send data in broadcast mode. In this case, this device is called a broadcaster, while the devices that receive the data are called observers.

What explained above is defined within a BLE specification called Generic Access Profile (GAP).


In this first tutorial you’ll learn how to develop a program that will periodically scan the air looking for BLE devices, that is a program which receives advertising packets and displays the data received in the serial console.

Before compiling a program which uses the Bluetooth controller, make sure (using menuconfig) that the controller is enabled (Component config -> Bluetooth):


Start your program with the required header files:

#include "esp_bt.h"
#include "esp_bt_main.h"
#include "esp_gap_ble_api.h"

You also need to initialize the NVS partition, used by the Bluetooth driver:


the Bluetooth controller of the esp32 chip supports both the classic and the low energy mode. If one of this two modes is not required in your program, you can release the memory the framework normally allocates to manage it using the esp_bt_controller_mem_release() command. In this example you’re not going to use the classic mode, so:


Now you can configure (using the default settings) the controller in BLE mode:

esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();

The esp-idf framework esp-idf includes the Bluedroid bluetooth stack. This library was developed by Broadcom and used by Android since version 4.2 Bluedroid is initialized and enabled with the following commands:


Now you’re ready to start scanning…

GAP, events

In a similar way to what you learned about the wifi driver, the bluetooth driver also runs in a thread separate from our program and communicates with it via events. In order to receive such events, you have to implement a callback function. Whenever the bluetooth driver has to notify an event, it will call that function.

The prototype of the callback function is:

static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);

You tell the driver which callback function has to use with the esp_ble_gap_register_callback() method:


The Bluetooth driver handles several events, there are the ones related to the scan process:


Before being able to start the scan process, you have to configure the scan parameters. The configuration is performed using the esp_ble_scan_params_t struct. It’s very important that the variable with the scan parameters is available during all the scan process; it’s therefore necessary to define it globally:

static esp_ble_scan_params_t ble_scan_params = {
  .scan_type              = BLE_SCAN_TYPE_ACTIVE,
  .own_addr_type          = BLE_ADDR_TYPE_PUBLIC,
  .scan_filter_policy     = BLE_SCAN_FILTER_ALLOW_ALL,
  .scan_interval          = 0x50,
  .scan_window            = 0x30

With the esp_ble_gap_set_scan_params() method you configure the scan process passing the struct defined above to the driver:


When the driver has finished the configuration, it calls the callback function with the event ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT. Depending on the event raised, the callback function also receives some parameters. The framework’s Programming Guide explains – for each event – the related parameters. For this event, it’s available the variable scan_param_cmpl that contains only the status parameter.

In the callback function you can use the switch statement to identify each event:

switch (event) {

and check if the configuration was successful with:

if(param->scan_param_cmpl.status == ESP_BT_STATUS_SUCCESS)

If so, you can start the scan process:


The parameter is the scan duration (in seconds).

Once the scan process has started, the driver raises the ESP_GAP_BLE_SCAN_START_COMPLETE_EVT event. For this event too it’s possible to verify the correct execution by reading the status parameter (pay attention: the name of the variable which contains the parameter changes!):

  if(param->scan_start_cmpl.status == ESP_BT_STATUS_SUCCESS)
    printf("Scan started\n\n");
    printf("Unable to start scan process");

GAP, scan process

During the scan process, for each advertising packet the chip receives the event ESP_GAP_BLE_SCAN_RESULT_EVT is raised.

This event contains some subevents. You can identify which subevent was raised reading the scan_rst.search_evt parameters. Two subevents are in particular interesting:


the first tells you that a device was detected, while the second one that the scan process completed.

For each detected device, various information is available. For now let’s print its address in the console:

  if(param->scan_rst.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
    printf("Device found: ADDR=");
    for(int i = 0; i < ESP_BD_ADDR_LEN; i++) {
      printf("%02X", param->scan_rst.bda[i]);
      if(i != ESP_BD_ADDR_LEN -1) printf(":");

The address is an uint8_t array, whose size is defined by the ESP_BD_ADDR_LEN constant. The address is normally displayed in hex form, with the bytes separated by :


Device list management

As explained above, the ESP_GAP_BLE_SCAN_RESULT_EVT event is raised everytime a device sends an advertising packet. This means that a single device will be detected multiple times during the scan process.

It’s therefore necessary to maintain a list of the known devices. In my Github repository you can find a test program that scans the network and prints all the detected devices.

You can verify if it works correctly comparing what the program detects with the BLE devices listed by a smartphone… for Android for example you can use the very good nRF Connect application by Nordic.

Here’s what my program detected:


and here’s the nRF Connect’s screenshot:



BLE with Bluno Beetle

Some time ago my friend Mauro Alfieri showed me an interesting development board produced by DFRobot and called Bluno Beetle (now Beetle Ble). It seemed the perfect board to start “playing” with the Bluetooth Low Energy (BLE) technology; therefore I ordered  one board directly from the DFRobot store.

I expected to receive the usual anonymous parcel with the board inside an antistatic plastic bag; DFRobot instead sends its products in an elegant cardboard box, protecting them with foam:

bluno-011 bluno-012

Bluno Beetle is really small and therefore perfect for wearable projects:

bluno-013 bluno-014

But what is it? Simplifying is a board, Arduino Uno compatible (it hosts the ATmega328P microcontroller) to which has been added the CC2540 chip from Texas Instruments to act as USB and BLE controller. The two chips communicate via a serial interface:


The CC2540 chip is actually a real microcontroller that runs a firmware developed by DFRobot. This firmware can be configured using AT commands. Normally the firmware runs in transparent mode, that is it acts as a “bridge” between the USB/BLE interfaces and the ATmega microcontroller. If you then connect the Bluno to your PC and activate the serial monitor, each character you type is forwarded to the ATmega and viceversa.

To send AT commands, first you have to enter the AT mode of the firmware, sending the + character 3 times (without appending a line ending). The firmware confirms the new mode with the sentence “Enter AT Mode”:


Now you can send the commands, appending the Windows line terminator (CRLF). For example to display the firmware version:


To exit the AT mode and go back to transparent mode, you have to send the AT+EXIT command.


It may happen that – if it’s the first time you connect the Bluno Beetle to your Windows PC – it is not correctly recognized:


The correct drivers are shipped with the Arduino IDE. You only need to do a manual installation specifying the path where you installed the IDE:


Windows will identify the new device as an Arduino Uno:



As explained above, if the firmware running on the CC2540 chip is in transparent mode, using the USB connection you can talk directly to the ATmega328P microcontroller. This means that you can program the microcontroller using the Arduino IDE without any problems… just choose Arduino Uno as board and select the correct serial port:


BLE and transparent mode

In transparent mode Bluno transmits via BLE each byte it receives from Arduino (the ATmega microcontroller) and – viceversa – it sends to Arduino each byte it receives from BLE.

In this first post let’s explore the demo application DFRobot provides; in a future post I’ll explain how to develop your application to interact with Bluno Beetle via BLE.

If you have an Android smartphone, you can directly install the apk file for the application named BlunoBasicDemo (application of which the source code is also available). In the same Github repository you can also find the source code of the iOS application, you have to compile by yourself.

Compile and upload the following sketch on the board:

unsigned long previous_time = 0;
void setup() {
void loop() {
  if (Serial.available() > 0) {
    int incomingByte =;
    Serial.print("New byte received: 0x");
    Serial.println(incomingByte, HEX);
  unsigned long actual_time = millis();
  if(actual_time - previous_time > 10000) {
    Serial.println("Hello world!");
    previous_time = actual_time;

The sketch reads the incoming bytes (coming from the app) and sends back to the app their hexadecimal value. Every 10 seconds moreover the sketch sends to the app the text Hello world!.

Launch the app. After having clicked on the Scan button, you can choose your Bluno board from the list of detected devices:


Every 10 seconds you should see a new HelloWorld! string appear. You can try to send a character (for example the letter “a”); you’ll receive an answer from Arduino (0x61 is indeed the hex code – in the ASCII table – for the letter “a”):


HID mode

Bluno also supports the HID (Human Interface Device) mode. When running in this mode, Bluno simulates an input peripheral (keyboard, mouse…) connected via BLE.

The AT command that enables this mode is:


After having enabled the HID mode, you can send one or more “keys” with:

  • AT+KEY=

you can send up to 3 different keys at a time, concatenating their codes with the + character. The codes to be used, according to the type (page) of the HID device, are listed in the USB specification.

The AT+KEY command notifies the pressure of a key on the keyboard.  It is therefore necessary, after a few moments, to send the AT+KEY=0 command to indicate that the key has been released; otherwise on the PC associated with Bluno you’ll see the character appear repeatedly!

Debug mode

Using two different AT commands you can enable the debug mode of the firmware. This mode allows to receive – via the USB connection – a copy of all the data sent and received through the BLE connection.

The two commands are:

  • AT+BLUNODEBUG=ON (copies the messages sent by the ATmega)
  • AT+USBDEBUG=ON (copies the messages received from BLE)


By default the first debug mode is active, while the second is disabled. You can verify it if you upload the sketch listed above: in the serial monitor you’ll see the Hello World! sentences but not the characters sent by the app.

Using Python to store data from many BLE devices


Erich Styger has written an article describing a technique he used to collect and store data from several BLE devices with Raspberry Pi and Python scripting:

BLE (Bluetooth Low Energy) sensor devices like the Hexiwear are great, but they cannot store a large amount of data. For a research project I have to collect data from many BLE devices for later processing. What I’m using is a Python script running on the Raspberry Pi which collects the data and stores it on a file

More details at