The objective of this ESP32 Bluetooth tutorial is to explain how to advertise a SPP service running on the ESP32 with SDP. The tests of this ESP32 tutorial were performed using a DFRobot’s ESP32 module device integrated in a ESP32 development board.
Introduction
The objective of this post is to explain how to advertise a SPP service running on the ESP32 with SDP.
In order to be able to access the ESP32 Bluetooth functionalities, we will be using the BTStack library port for the ESP32. If you haven’t yet configured them, please check this introductory post on how to do it.
For testing the discovery of the ESP32 Bluetooth service we will be using Pybluez, a Bluetooth library for Python. This previous tutorial explains how to get start with it.
Note that SPP (Serial Port Profile) works on top of RFCOMM, and thus we will also need to deal with this lower layer. You can check this previous tutorial which explains how to receive data on the ESP32 using RFCOMM.
SDP (Service Discovery Protocol) provides the functionality needed for applications to discover which services are available and their characteristics [1]. Thus, it is the mechanism that we need in order for making our SPP service discoverable for other Bluetooth devices, removing the need for them to know the ESP32 address beforehand.
This tutorial is based on the spp_counter example from the BTStack library, which I encourage you to try. It is a very complete example with some more functionalities being demonstrated.
The tests of this ESP32 tutorial were performed using a DFRobot’s ESP32 module device integrated in a ESP32 development board.
The Python code
The code for this test will be very simple from the Python side. As usual, we start it by importing the module needed for the Bluetooth functionality.
from bluetooth import *
Then, to start the lookup for available services, we simply call the find_service function. You can find more detail about it in the API documentation of Pybluez.
This function supports 3 optional arguments which default to none: the name, the UUID and the address. If one or more of them are specified, they will be using as filtering criteria when looking up for nearby services. If not specified, all services detected will be returned.
In our case, for simplicity, none of this arguments will be specified and thus we should receive the information of all nearby services, which includes the ESP32 one. We will store the output of this function call in a variable, so we can analyse it later.
services=find_service()
The output of this call will be a list of dictionaries with the information about each discovered device. You can check in more detail in the API specification all the parameters available, but we will be accessing the port, the name and the host of the service.
So, you can check bellow the full source code, which already includes iterating through the output of the previous function call the the print of all the values. In this SDP introductory tutorial we will not be using them for anything else, but in a real scenario they would be used for connecting to the desired service.
from bluetooth import *
services=find_service()
for i in range(len(services)):
name = str(services[i]["name"] )
port = str(services[i]["port"] )
host = str(services[i]["host"] )
print "name: " + name
print "port: " + port
print "host: " + host
The ESP32 code
We will start our code by including the btstack.h header library, which will provide us the functionality needed.
We will also need a buffer for registering our service, which we will declare as a static global variable. Its use will be seen in some functions call we will be doing shortly.
#include "btstack.h"
static uint8_t spp_service_buffer[150];
Then, on the btstack main function, we start by initializing the L2CAP layer, with a call to the l2cap_init function. Since we are going to advertise a RFCOMM service, we also init the RFCOMM layer with a call to the rfcomm_init function.
After this we need to register our RFCOMM service, with a call to the rfcomm_register_service function. You can check how to do it in more detail in this previous post.
Note that this function receives as first input a function for handling data packets. Nevertheless, since we are just going to test the advertisement part of the service, we can pass NULL to it.
The other two arguments are the channel for the RFCOMM communication and the maximum packet size.
l2cap_init();
rfcomm_init();
rfcomm_register_service(NULL, 1, 0xffff);
Then we will use the memset function to set all the values of our previously declared service buffer to 0.
memset(spp_service_buffer, 0, sizeof(spp_service_buffer));
Then, we need to call the spp_create_sdp_record to create a SDP record for our SPP server. This functions needs an empty buffer [2] and this is why we declared and initialized the previously mentioned variable.
As additional parameters, the function receives a service record handle, the RFCOMM channel and the name of the service [3].
Remember that from the previous RFCOMM initialization we have it on channel 1. As a name for the service, I will be calling it “My service”, but you can use another value. Both these values should be coherent with the results that we will obtain when running the Python code.
Note that SPP is a Bluetooth profile that exists on top of RFCOMM, as mentioned in the introductory section. At the BTStack documentation page you can check a very detailed layer diagram on how the different profiles and protocols interact.
spp_create_sdp_record(spp_service_buffer, 0x10001, 1, "My service");
Then, after creating this record, we register the service on the SDP layer. To do so, we just need to call the sdp_register_service function, which receives as input the previously mentioned spp_service_buffer variable.
sdp_register_service(spp_service_buffer);
Next, we need to set up the SDP layer. To do so, we call the sdp_init function, which receives no arguments.
sdp_init();
To finalize, we just make our device discoverable, set its name to some value (in this example, we are setting it to “ESP32”) and start the Bluetooth controller hardware.
gap_discoverable_control(1);
gap_set_local_name("ESP32");
hci_power_control(HCI_POWER_ON);
The final ESP32 code can be seen bellow.
#include "btstack.h"
static uint8_t spp_service_buffer[150];
int btstack_main(int argc, const char * argv[]){
l2cap_init();
rfcomm_init();
rfcomm_register_service(NULL, 1, 0xffff);
memset(spp_service_buffer, 0, sizeof(spp_service_buffer));
spp_create_sdp_record(spp_service_buffer, 0x10001, 1, "My service");
sdp_register_service(spp_service_buffer);
sdp_init();
gap_discoverable_control(1);
gap_set_local_name("ESP32");
hci_power_control(HCI_POWER_ON);
return 0;
}
Testing the code
To perform the testing, just compile and upload the ESP32 code to your device. Please check the initial BTStack tutorial that provides a more detailed explanation on how to do it. Nevertheless, it follows the regular compile and upload procedure from IDF, with the make flash command.
After the code is running on the ESP32 and upon executing the Python code, you should get a result similar to the one shown in figure 1. As can be seen, the name is the one we defined in the ESP32 code. The port also matches the channel we defined and the host contains the address of my ESP32 device. Naturally, the address of your device should be different from mine.
Figure 1 – Output of the Python program.