ESP32 Bluetooth

Материал из
Перейти к: навигация, поиск


The ESP32 has native Bluetooth support (version 4.2). This means that it can interact with Bluetooth devices such as keyboards, mice and cell phones. Let us review what Bluetooth means for us. Bluetooth is a wireless communication protocol/technology that provides data transfer over a radio signal. Let us assume that ESP32 is one end of the connection and any other Bluetooth device can be at the other. For security purposes, arbitrary Bluetooth devices can't simply be "used" without some explicit authorization. For example, it would be very wrong if I could bring my Bluetooth headset near your phone and start listening to your calls. To achieve security, a process called "pairing" needs to be performed. This achieves a level of trust between the two Bluetooth devices such that they subsequently allow connection without having to be re-paired.

Bluetooth specification

Bluetooth is a specification for wireless communication between multiple electronic devices. At present, there are two primary standards … these are Bluetooth (Classic) and Bluetooth LE. The "LE" stands for Low Energy and is the specification for devices that wish to be powered by simple batteries and yet have sufficient life span. In the Bluetooth story, we have devices which are "masters" and devices which are "slaves". A slave can only form a connection and communicate with a master while a master can form concurrent connections with multiple slaves. One slave can not directly communicate with another slave. Instead it must communicate with the master and the master relay the request. The simplest communication is one master and one slave but if we have multiple slaves connected to the same master, the resulting "network" is termed a "piconet". Each device that participates in the conversation has a unique address that is a 48 bit value commonly written as 12 hex values (6 bytes). This address is known as the "Bluetooth Device Address" and may be seen in other documentation abbreviated to "BD_ADDR". The encoding of a Bluetooth address is that the first 24 bits encode the organization responsible for allocating the remaining address and the remaining 24 bits are the address itself. However the full 48 bits are the complete identity of the device. As well as having a unique address, each device can have a symbolic name to help us meaningfully identify it. This is termed the display name. The display name is only a mapping to a Bluetooth address and it is really the address that is used to distinguish one device from another. It is also important to note that there are no uniqueness constraints on device names. Multiple devices may select the same device name. Let us assume that initially, we have two devices and neither of them know about the other. What must now happen is a discovery process. One of them will broadcast an "inquiry" request. Devices receiving the inquiry can respond with their own existence by transmitting their own address and possibly additional information. It is common that the response to an inquiry does not contain the display name of the responding device. If the display name is needed, the inquirer can transmit a directed request now that it knows the address of the responding device which will solicit the name as a further response. A device does not have to respond to an inquiry request. It has a property setting called an "inquiry scan" that controls whether or not it responds to such. If the attribute is on, then it will respond to an inquiry request and if off, then it will not respond. Think of the phrase "inquiry scan" as the devices choice as to whether it performs the action of "scanning for inquires". Once the two devices know each other's addresses, a connection can be formed between them through a process known as "paging". Again, a device does not have to service a received connection request. It has a property setting called a "paging scan" which controls this. If on, then a paging request causes a new connection to be formed. If off, it will not accept a new connection request. Once a connection is formed between the devices, that connection can be maintained in a variety of states, the most common being active. However other states are available and are used to save power when there is no active communication of data anticipated. In order to permit devices to communicate with each other, there has to be an element of security involved. We usually don't want arbitrary devices to be able to connect with each other and share arbitrary information. To achieve this, a process called "bonding" is enacted. Bonding is achieved through the notion of "pairing". In pairing, the devices exchange their addresses, names and other data and generate keys that they share with each other. Pairing typically requires an explicit interaction from the user to permit the pairing to succeed. The user interaction can be as simple as "I approve this device" with a button click or it can be richer with the entry of a pass-code to authenticate and prove that one is who one claims to be. The Bluetooth protocol provides support for different classes of power. This translates directly into the signal strength of the radio. Remember that the more power used by the radio, the heavier the drain on the power source. If the power source is batteries, the more power used to transmit data, the shorter the life of the batteries. At the lower levels, Bluetooth takes care of exchanging data between partners. However, there is much more to Bluetooth than just simple data exchange. In order to provide interoperability between devices built by multiple manufacturers, higher level protocols called "profiles" have been defined. These profiles define "what" is transmitted over Bluetooth for a given device function. Some of the profiles we will come across include: • HSP – Head Set Profile (eg. a Bluetooth ear piece). • HFP – Hands Free Profile (eg. Bluetooth communication in a car). • HID – Human Interface Device (eg. a keyboard or mouse). • SPP – Serial Port Profile. • A2DP – Advanced Audio Distribution profile (eg. connection to Bluetooth speakers). • AVRCP – Audio Visual Remote Control Profile. Knowing that these protocols exist, it is not sufficient that two Bluetooth devices are in range of each other, they must also both support the same profile that is desired to be used. At a higher level that the connection are the transport protocols available to us. These include: • RFCOMM – Radio Frequency Communications Protocol. This protocol provides a reliable stream oriented transmission. Loosely, you can compare it to TCP. • L2CAP – Logical Link Control and Adaption Protocol. This is a packet oriented protocol. RFCOMM builds on L2CAP. • ACL – Asynchronous Connection oriented Logical protocol. L2CAP builds on ACL. • SCO – Voice quality audio protocol. To allow multiple conversations to be processed in parallel, the concept of the "port" is introduced. This is similar to a TCP/IP port number. L2CAP ports can be odd numbered values between 1 and 32767. For RFCOMM, the port numbers are called channels and are between 1 and 30. In the Bluetooth documentation, ports are referred to as "Protocol Service Multiplexers" or "PSMs". Certain port numbers used by L2CAP are designated as reserved for well defined purposes. See also: • Introduction to Bluetooth Low Energy

Bluetooth UUIDs

A UUID is a 16 byte number (128 bits). They are commonly written in hexadecimal with 1 byte corresponding to 2 hex digits. The most common written format is 4-2-2-2-6. XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX When one receives a UUID, it is the value of the UUID that is used to describe the nature of the service or data. Each different type of service will have its own unique UUID. To reduce transmission overhead, the specification defines some well described services. If one implements one of those services, then we don't need to transmit the whole 128 bit UID but can get away with less. A special UUID of the form: XXXXXXXX-0000-1000-8000-00805F9BE4FB is available where only the first 4 bytes (32 bits) are needed to identify the type of service.

Bluetooth GAP

The Generic Access Protocol (GAP). It is GAP that determine which devices can interact with each other. In the GAP story there are primarily two classes of "things". There is the central device and the peripheral device. We can send advertizing using the Advertizing Data payload or the Scan Response payload. A peripheral device will constantly transmit its advertizing payload which can contain up to 31 bytes of data. The peripheral transmits its advertizing data every advertizing interval period. The Advertizing Data payload is present in all BT devices. If the central receiver of the advertizing data payload wishes, it can request a scan response and the peripheral will send back a scan response payload. When a peripheral is transmitting advertizing data, we can think of it as being in the mode of broadcasting. It is transmitting its data which may or may not be seen by a corresponding observer. The concept of BLE advertizing is powerful beyond just finding devices to form subsequent connections. The advertizing packets can contain data in their payload. If the data being transmitted does not need to be secure (for example, the outside temperature), then we have the core of an interesting solution in its own right. The peripheral can simply broadcast its packets of data and the central can receive them without the need to form a connection. The receiver can then examine the payload and receive the data, as long as the data is small enough. In this story, the peripheral is performing the role of a "broadcaster" while the central would be performing the role of an "observer". When working in this mode, do realize that the data is flowing in only one direction … from the publisher to the observer. If you need to send response back, you will need to form a connection. The rate of advertizing can be set to be a period between 20ms and 10.24seconds in steps of 0.625ms. Thus we can transmit our advertizing packets frequently or slowly. See also: • A BLE Advertising Primer

GAP Advertizing data

There is theory and there is practice and studying BLE gives us the opportunity for both. Let us focus on the notion of a BLE peripheral broadcasting advertizing messages. At a high level it will look like: The advertized data payload has a maximum size of 31 bytes. Each payload is composed of one or more data structures where the format of each structure is: Length Advertising Data Type Data ... The Length is 1 byte in size and indicates how many bytes, following the length byte, this record will be. The number of records in a payload is variable but, of course, the total amount of data has to be 31 bytes or less. Either a length of 0 or an ignorable structure type can be used. Here is an example of a real payload that I received: 020105020A000319C1030302E0FF11094D4C452D3135202020202020202020 Now we can parse this as follows: 02 01 05 – advertising type 0x01 02 0a 00 – advertising type 0x0A 03 19 c1 03 – advertising type 0x19 03 02 e0 ff – advertising type 0x02 11 09 4d 4c 45 2d 31 35 20 20 20 20 20 20 20 20 20 – advertising type 09 Here is a simple routine that will step through the structures … while(!finished) { length = *payload; payload++; if (length != 0) { ad_type = *payload; payload += length; ESP_LOGD(tag, "Type: 0x%.2x, length: %d", ad_type, length); } sizeConsumed += 1 + length; if (sizeConsumed >=31 || length == 0) { finished = 1; } } // !finished Now that we know how to see the structures and access them, the obvious question is what do each of the structures "mean". For most, they are architected in the BLE specification. Each structure (after the length) has a single byte that is the "advertising data type". These single byte number codes are described here: In our ESP32 environment, we have constant definitions for each: • ESP_BLE_AD_TYPE_FLAG (0x01) – The advert contains a byte of flags that are defined as following: ◦ Bit 0 – LE Limited Discoverable Mode ◦ Bit 1 – LE General Discoverable Mode ◦ Bit 2 – BR/EDR is NOT supported. ◦ Bit 3 – Indicates whether LE and BR/EDR Controller operates simultaneously ◦ Bit 4 – Indicates whether LE and BR/EDR Host operates simultaneously ◦ Bits 5-7 – Reserved. • ESP_BLE_AD_TYPE_16SRV_PART (0x02) – Incomplete list of 16bit service class UUIDs. • ESP_BLE_AD_TYPE_16SRV_CMPL (0x03) • ESP_BLE_AD_TYPE_32SRV_PART (0x04) • ESP_BLE_AD_TYPE_32SRV_CMPL (0x05) • ESP_BLE_AD_TYPE_128SRV_PART (0x06) – Incomplete list of 16bit service class UUIDs. • ESP_BLE_AD_TYPE_128SRV_CMPL (x07) • ESP_BLE_AD_TYPE_NAME_SHORT (0x08) – Shortened local name. • ESP_BLE_AD_TYPE_NAME_CMPL (0x09) – Complete local name. • ESP_BLE_AD_TYPE_TX_PWR (0x0A) • ESP_BLE_AD_TYPE_DEV_CLASS (0x0D) • ESP_BLE_AD_TYPE_SM_TK (0x10) • ESP_BLE_AD_TYPE_SM_OOB_FLAG (0x11) • ESP_BLE_AD_TYPE_INT_RANGE (0x12) • ESP_BLE_AD_TYPE_SOL_SRV_UUID (0x14) • ESP_BLE_AD_TYPE_128SOL_SRV_UUID (0x15) • ESP_BLE_AD_TYPE_SERVICE_DATA (0x16) • ESP_BLE_AD_TYPE_PUBLIC_TARGET (0x17) • ESP_BLE_AD_TYPE_RANDOM_TARGET (0x18) • ESP_BLE_AD_TYPE_APPEARANCE (0x19) – It is likely this conforms to the assigned numbers found here and • ESP_BLE_AD_TYPE_ADV_INT (0x1A) • ESP_BLE_AD_TYPE_32SOL_SRV_UUID (0x1B) • ESP_BLE_AD_TYPE_32SERVICE_DATA (0x1C) • ESP_BLE_AD_TYPE_128SERVICE_DATA (0x1D) • ESP_BLE_AD_MANUFACTURER_SPECIFIC_TYPE (0xFF) – Custom payload. With this in mind, if we look at our sample data … we can see that it means: 02 01 05 – advertising type 0x01 – ESP_BLE_AD_TYPE_FLAG 02 0a 00 – advertising type 0x0A – ESP_BLE_AD_TYPE_TX_PWR 03 19 c1 03 – advertising type 0x19 – ESP_BLE_AD_TYPE_APPEARANCE 03 02 e0 ff – advertising type 0x02 – ESP_BLE_AD_TYPE_16SRV_PART 11 09 4d 4c 45 2d 31 35 20 20 20 20 20 20 20 20 20 – advertising type 0x09 – ESP_BLE_AD_TYPE_NAME_CMPL Now we can consult the specification and determine what the data part of each advertising type means and we have our information. Because examining the advertising data of a GAP message is so important and so common, the ESP-IDF contains a very useful utility function that takes as an input the advertisement data and looks for an advertising type structure within it of a defined type. The function is called esp_ble_resolv_adv_data(). See also: • esp_ble_resolve_adv_data

Advertizability – limited and general

Is "advertizability" even a real word? I think not … but it describes what we want to cover. When we have a bluetooth peripheral, we somehow need to make it available to be discovered by a bluetooth central in order for the central to make requests of the peripheral. The way we achieve this is by making the peripheral visible by it broadcasting its advertising packets. When a peripheral starts advertising, it can tag the advert as either limited-discoverable or general-discoverable. The notion here is that limited-discoverable devices have "recently" been enable to start broadcasting while "general-discoverable" devices usually broadcast continuously. So what you may ask? Well, think of a user looking for a device. It is not uncommon for a new device to be added by pressing some button on it to start it broadcasting so that it can be found and paired. We want this new device to show up higher in the list of found devices than others … and this can be achieved by recognizing that a device which only broadcasts for a limited period of time will enable its "limited-discoverable" flag and provide a hint to the software running on the central that the device is likely going to be the one the user is looking for. Now we have introduced the concept of an advertizing packet having two flags called "limited-discoverable" and "general-discoverable", only one of which may be set at a time. This leaves one more choice which is that neither "limited-discoverable" nor "general-discoverable" are set. This is an allowed transmission and the packet will still be detected by the observer however this indicates that no connection should be attempted and that the peripheral should not be listed to the user.

Filtering devices=

When we are looking for a device, we may find more devices than we want. Rather than present all devices to the user, we can choose to filter the set of available devices and hide the ones that we believe are of no interest. For example, if I am running a heart-rate monitor application on my phone, I'm really only interested in devices that can provide such information. My latest bluetooth headset or the outside temperature is not what I'm looking for. What we want to do is filter the set of ALL found devices down to only the ones that match some criteria of interest to us. In order for that to happen, each device must not only advertize its existence, but also supply information that can be used to include or eliminate it from selection.

Performing a scan

There isn't much value in generating advertising data if no-one is listening. In BLE the act of listening for advertising data is called "scanning". To perform a scan in the ESP32 we perform the following tasks: 1. We register a callback function to handle the received data. 2. We define the parameters for how we would like the scan to be performed. 3. We ask the ESP32 to start scanning. Translating these into ESP32 APIs we have esp_ble_gap_register_callback() to register our callback function to be invoked when a data arrives. We have the esp_ble_gap_set_scan_params() to setup how we wish the scanning to be performed. We have esp_ble_gap_start_scanning() to initiate a scan request. If we need to interrupt our scanning before the specified duration of scanning has completed, we can call esp_ble_gap_stop_scanning(). The function registered when we call esp_ble_gap_register_callback() is where the majority of our function will happen. The parameter to this function is itself a C function which the following signature: void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) The param is a union of structures. Event Type Data Property ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT adv_data_cmpl ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT adv_data_raw_cmpl ESP_GAP_BLE_ADV_START_COMPLETE_EVT adv_start_cmpl ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT scan_param_cmpl ESP_GAP_BLE_SCAN_RESULT_EVT scan_rst ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT scan_rsp_data_raw_cmpl ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT scan_rsp_data_cmpl ESP_GAP_BLE_SCAN_START_COMPLETE_EVT scan_start_cmpl The event parameter defines the type of event we have received. Event types include: • ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT – Raw advertizing data operation complete. • ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT – Called when advertizing data set is complete. Structure parameter is called scan_rsp_data_cmpl. ◦ esp_bt_status_t status – The status of the event. • ESP_GAP_BLE_ADV_START_COMPLETE_EVT – Called when advertizing scan startup is complete. The parameter is a property called scan_start_cmpl which contains: ◦ esp_bt_status_t status – The status of the event. • ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT – Called when scan parameters set complete. Structure parameter is called scan_param_cmpl. ◦ esp_bt_status_t status • ESP_GAP_BLE_SCAN_RESULT_EVT – The param is an instance of esp_ble_gap_cb_param_t. Called when one scan result is ready. The structure parameter is called scan_rst. ◦ esp_gap_search_evt_t search_evt – Choices are: ▪ ESP_GAP_SEARCH_INQ_RES_EVT – We have received a search result. ▪ ESP_GAP_SEARCH_INQ_CMPL_EVT – The search is complete. ▪ ESP_GAP_SEARCH_DISC_RES_EVT ▪ ESP_GAP_SEARCH_DISC_BLE_RES_EVT ▪ ESP_GAP_SEARCH_DISC_CMPL_EVT ▪ ESP_GAP_SEARCH_DI_DISC_CMPL_EVT ▪ ESP_GAP_SEARCH_SEARCH_CANCEL_CMPL_EVT ◦ esp_bd_addr_t bda – The address of the device. 6 bytes of data. ◦ esp_bt_dev_type_t dev_type – One of: ▪ ESP_BT_DEVICE_TYPE_BREDR ▪ ESP_BT_DEVICE_TYPE_BLE ▪ ESP_BT_DEVICE_TYPE_DUMO ◦ esp_ble_addr_type_t ble_addr_type – One of ▪ BLE_ADDR_TYPE_PUBLIC ▪ BLE_ADDR_TYPE_RANDOM ▪ BLE_ADDR_TYPE_RPA_PUBLIC ▪ BLE_ADDR_TYPE_RPA_RANDOM ◦ esp_ble_evt_type_t ble_evt_type – One of ◦ int rssi – The signal strength. ◦ uint8_t ble_adv[ESP_BLE_ADV_DATA_LEN_MAX] – The advertized data. ◦ int flag ◦ int num_resps – The number of responses received. This is valid when the search_evt type is ESP_GAP_SEARCH_INQ_CMPL_EVT. • ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT – ???. • ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT – Called when the scan response data set is complete. Structure parameter is called: scan_rsp_data_cmpl. ◦ esp_bt_status_t status • ESP_GAP_BLE_SCAN_START_COMPLETE_EVT – ???. A typical series of events received might be: • ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT • ESP_GAP_BLE_SCAN_START_COMPLETE_EVT • ESP_GAP_BLE_SCAN_RESULT_EVT – ESP_GAP_SEARCH_INQ_RES_EVT • … • ESP_GAP_BLE_SCAN_RESULT_EVT – ESP_GAP_SEARCH_INQ_CMPL_EVT See also: • esp_ble_gap_register_callback • esp_ble_gap_set_scan_params • esp_ble_gap_start_scanning • esp_ble_gap_stop_scanning

Performing advertising

If our ESP32 is going to be a peripheral, then it will be advertising its existence. The ESP-IDF provides some APIs to achieve that task. At a high level: 1. Call esp_ble_gap_config_adv_data() to specify the content of our periodic advertisement. 2. Call esp_ble_gap_start_advertising() to initiate the periodic advertisement. While superficially simple, we need to consider all the distinct parameters that are available to us … and there are many. We will start with the esp_ble_gap_config_adv_data(). This is where we specify the content of the advertisement payload. An example structure would be: static esp_ble_adv_data_t test_adv_data = { .set_scan_rsp = false, .include_name = true, .include_txpower = true, .min_interval = 0x20, .max_interval = 0x40, .appearance = 0x00, .manufacturer_len = 0, .p_manufacturer_data = NULL, .service_data_len = 0, .p_service_data = NULL, .service_uuid_len = 32, .p_service_uuid = test_service_uuid128, .flag = 0x2, }; Once we have started advertising, we can check the published information using: $ sudo hcitool lescan See also: • esp_ble_gap_config_adv_data • esp_ble_gap_start_advertising • esp_ble_gap_stop_advertising

Bluetooth GATT

The Generic Attribute Protocol (strangely called GATT) provides a mechanism for passing data in a standard form. GATT is always present in BLE. Think of GATT as a way to send and receive data that is "remembered" at the GATT server (while it is running). A client can request the values of data items as well as receive asynchronous notifications as events when something interesting happens at the GATT server. At the high level of the protocol there is the concept of a service. The service is the grouping of functionally related attributes. Within a service are characteristics where a characteristic is a property of that service. Each characteristic type is identified by its own UUID value. In addition a characteristic contains a value, properties, security and may also include descriptors. Within the specification of GATT we have the notion of two roles … namely that of a client and that of a server. A GATT server is commonly a passive entity that waits for requests from GATT clients and services them as they arrive. When a GATT client starts, it assumes little about the GATT server and first performs an inquiry upon the server to determine its characteristics. Every BLE device must include the capability to be a GATT server. The GATT server manages attributes that can be read by or written from a GATT client. How these attributes are stored by the GATT server internally is not defined in the specification and left to the implementers to choose. Within a GATT server, every attribute has a unique "handle" that is 16 bits in length. The value of an attribute is governed by its data type which is defined by a UUID. It has a maximum length of 512 bytes. An attribute also has permissions which govern what one can do against an attribute. The permissions include: • None • Readable • Writable • Both readable and writable This story of raw attributes is further specified in the GATT protocol by the notion of services and characteristics. Think of a service a logical description that a GATT server is prepared to do something that is well defined. If it claims to provide a service, then it must adhere to the contract described by that service. This contract is defined by a set of characteristics and the set of those make up the service. See also: • Generic Attribute Profile (GATT) • generic attributes (gatt) and the generic attribute profile • GATT-Based specifications • GATT services – Assigned numbers • GATT delarations – Assigned number • GATT characteristics – Assigned numbers • gatttool

Being a GATT client

From the ESP32 perspective, to be a GATT client we perform: 1. Register a callback to receive GATT events using esp_ble_gattc_register_callback(). 2. Call esp_ble_gattc_app_register() to register this application. 3. Open a connection to the GATT server using esp_ble_gattc_open(). 4. When an open event arrives, execute a search using esp_ble_gattc_search_service(). When we call esp_ble_gattc_open() we are requesting to open a GATT connection to a specific device. This request is non-blocking and will execute in the background. Eventually we will receive a GATT event indicating the outcome. The event type will be ESP_GATTC_OPEN_EVT. As part of the response data from the ESP_GATTC_OPEN_EVT we will receive a connection identifier (conn_id). This conn_id can be loosely thought of as a socket to the partner device. Once we have formed a connection to the partner device, we can ask it about the services that it offers. We do this by calling esp_ble_gattc_search_service() function. Like other BLE mechanisms, this is an asynchronous operation that results in a series of GATT events being generated. For each service possessed by the device, an ESP_GATTC_SEARCH_RES_EVT is fired and finally, when we have seen all the services, we get an ESP_GATTC_SEARCH_CMPL_EVT. This is illustrated in the following diagram: For each of the services we get back we can start to invoke those services on the device. See also: • esp_ble_gattc_register_callback • esp_ble_gattc_app_register • esp_ble_gattc_open • esp_ble_gattc_search_service • GATT Services

Being a GATT Server

The high level of being a GATT server is: esp_bt_controller_init() esp_bt_controller_enable() esp_bluedroid_init() esp_bluedroid_enable() esp_ble_gatts_register_callback() esp_ble_gap_register_callback() esp_ble_gatts_app_register() The call to esp_ble_gatts_app_register() registers our application. This passes control back to the BLE subsystem and, when ready, will call back the GATT server event handler with an event type of ESP_GATTS_REG_EVT. When we receive that, we then do the next part of the setup: esp_ble_gap_set_device_name() esp_ble_gap_config_adv_data() esp_ble_gatts_create_service() To test that all is working, we can run the hcitool from a Linux system: $ sudo hcitool lescan 24:0A:C4:00:00:96 MYDEVICE See also: • esp_ble_gatts_register_callback • esp_ble_gap_register_callback • esp_ble_gap_set_device_name • esp_ble_gap_config_adv_data • esp_ble_gatts_app_register • esp_ble_gatts_create_service

Service Discovery Protocol

When a client application wishes to request that a connection be established, the client needs to know the port number on which the server is listening. In TCP/IP land, this is achieved by having the client and the server share the implicit knowledge of the port number to use. In Bluetooth, extra functions have been made available. Specifically, there is a service available called the Service Discovery Protocol or "SDP". At the server, when a service is offered, the port number of that service is registered to the local SDP. When a client now wishes to use the target service, it first requests endpoint information from the SDP running on the server. The SDP returns the endpoint information and the client now has all the information it needs to create a direct connection to the desired target service. The unit of information managed by the SDP server is called a "service record" or "SDP record". A command line interface tool called sdptool is available to examine a Bluetooth device's SDP data. A simple command is: $ sdptool browse <Bluetooth Address> This returns a series of records of the form: • Service Name • Service Description • Service Provider • Service RecHandler • Service Class ID List • Protocol Descriptor List • Profile Descriptor List • Language Base Attr List See also: • man(1) – sdptoo l

ESP32 and Bluetooth

Logic appears to be: esp_bt_controller_init() esp_bt_controller_enable(ESP_BT_MODE_BTDM) esp_bluedroid_init() esp_bluedroid_enable() esp_ble_gap_register_callback() esp_ble_gattc_register_callback() esp_ble_gattc_app_register() esp_ble_gap_set_scan_params() esp_ble_gap_start_scanning(20)

Debugging ESP32 Bluetooth

The ESP32 bluetooth implementation is built upon an environment called Bluedroid. The reason this becomes important is that for full understanding of the the ESP32 Bluetooth environment, we need to understand the Bluedroid environment as well. For example, the art of getting lower level diagnostics drops us down to the Bluedroid APIs. For example:

  1. include <gatt_api.h> // bluedroid include

… GATT_SetTraceLevel(6); will switch on GATT level tracing.

Bluetooth C Programming in Linux

Within the C / Linux environment we have an implementation of the API stack called "BlueZ". The BlueZ implementation supports RFCOMM, L2CAP, SCO and HCI. In order to perform Bluetooth programming we must install the package called "libbluetooth-dev" using: $ sudo apt-get install libbluetooth-dev When compiling, we need to link with libbluetooth. See also: • An Introduction to Bluetooth Programming


Retrieve a handle to the specified Bluetooth device or the first available if NULL is supplied. int hci_get_route(bdaddr_t *bdaddr)


Open the specified device and get a handle to that device. The returned value is a socket. int hci_open_dev(int dev_id) If the return is -1 then an error was encountered and the details of the error can be found in errno.


int hci_inquiry( int dev_id, int len, int max_rsp, const uint8_t *lap, inquiry_info **ii, long flags) where: • dev_id – the device id of the adapter as retruned by hci_get_route. • len – The duration of the scan * 1.28 seconds. • max_rsp – The maximum number of responses we are willing to accept. • lap – may be NULL • ii – Pointer to an array of inquiry_info structures to be populated. The storage must exist and be at least of size max_rsp * sizeof(inquiry_info). • flags ◦ 0 – Cached results allowed ◦ IREQ_CACHE_FLUSH – Any cached values are discarded and only new responses will be used. The inquiry_info is a struct containing: • bdaddr_t bdaddr • uint8_t pscan_rep_mode • uint8_t pscan_period_mode • uint8_t pscan_mode • uint8_t dev_class[3] – The device class is encoded in the assigned numbers – baseband. • uint16_t clock_offset See also: • Assigned numbers – baseband


Retrieve the display name of a specified device. int hci_read_remote_name( int hci_sock, const baddr_t *addr, int len, char *name, int timeout ) • len – the size of the name buffer to hold the display name. • name – a buffer to hold the display name. • timeout – Maximum number of milliseconds to wait before giving up. On return, a value of 0 indicates success.


Convert a string representation of a Bluetooth address into an address. str2ba(const char *str, bdaddr_t *ba) Where str is the string representation of the Bluetooth address and ba is a pointer to a bdaddr_t structure to hold the resulting address.


Convert a Bluetooth address to a string. The string buffer must be at least 18 bytes long. ba2str(const bdaddr_t *ba, char *str) The ba is a pointer to a bdaddr_t structure while str is the buffer to be populated with the string representation. sdp_connect() sdp_service_search_attr_req() sdp_record_register()

Bluetooth Audio

Bluetooth speakers and headphones are common so a natural question would be how to get sound out of them from the Pi. The answer is to install an application called PulseAudio. $ sudo apt-get install pulseaudio pulseaudio-module-bluetooth The Bluetooth profile we want to work with is called A2DP (Advanced Audio Distribution Profile).

Bluetooth RFCOMM

A serial protocol is available via Bluetooth and is called "RFCOMM" for "Radio Frequency Communication". When programming with C, we want to create a socket using: int s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); A socket address structure for Bluetooth RFCOMM is a struct called sockaddr_rc which contains: • sa_family rc_family – This will always be AF_BLUETOOTH. • bdaddr_t rc_bdaddr – The address of the device to which we wish to connect or listen upon. If any local Bluetooth adapter will suffice when we are a server, we can supply BDADDR_ANY. • uint8_t rc_channel – The channel to which we wish to connect. To be a client of an RFCOMM server, we would use:

  1. include <stdio.h>
  2. include <unistd.h>
  3. include <sys/socket.h>
  4. include <bluetooth/bluetooth.h>
  5. include <bluetooth/rfcomm.h>

int main(int argc, char *argv[]) { if (argc != 2) { printf("Usage: %s bdaddr\n", argv[0]); return 0; } struct sockaddr_rc addr = {0}; int s, status; char *dest = argv[1]; s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); addr.rc_family = AF_BLUETOOTH; addr.rc_channel = 1; str2ba(dest, &addr.rc_bdaddr); status = connect(s,(struct sockaddr *)&addr, sizeof(addr )); if(status == 0) { status = send(s, "hello!", 6, 0); } if(status < 0) { perror("connect"); } close(s); return 0; } while to be a server we would use:

  1. include <stdio.h>
  2. include <unistd.h>
  3. include <sys/socket.h>
  4. include <bluetooth/bluetooth.h>
  5. include <bluetooth/rfcomm.h>

int main(int argc, char **argv) { struct sockaddr_rc loc_addr = { 0 }, rem_addr = { 0 }; char buf[1024] = { 0 }; int s, client, bytes_read; socklen_t opt = sizeof(rem_addr); // allocate socket s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); // bind socket to port 1 of the first available // local bluetooth adapter loc_addr.rc_family = AF_BLUETOOTH; loc_addr.rc_bdaddr = *BDADDR_ANY; loc_addr.rc_channel = (uint8_t) 1; bind(s, (struct sockaddr *)&loc_addr, sizeof(loc_addr)); // put socket into listening mode listen(s, 1); // accept one connection client = accept(s, (struct sockaddr *)&rem_addr, &opt); ba2str(&rem_addr.rc_bdaddr, buf); fprintf(stderr, "accepted connection from %s\n", buf); memset(buf, 0, sizeof(buf)); // read data from the client bytes_read = read(client, buf, sizeof(buf)); if (bytes_read > 0) { printf("received [%s]\n", buf); } // close connection close(client); close(s); return 0; } See also: • RFCOMM with TS 07.10 • man(1) – rfcomm • An Introduction to Bluetooth Programming

Bluetooth tools


In TCP/IP networking, we have a tool called ping which sends an ICMP packet over the network to which the partner responds. We can use this command to determine the "liveness" of the partner as well as get the round trip response times. For Bluetooth, we have a similar tool called "l2ping". This command sends a L2CAP echo request to the partner and waits for the response. At the highest level, we use: $ sudo l2ping <bd_addr> where bd_addr is the address of the target device. Here is an example output: $ sudo l2ping 00:1A:7D:DA:71:13 Ping: 00:1A:7D:DA:71:13 from B8:27:EB:62:03:9F (data size 44) ... 44 bytes from 00:1A:7D:DA:71:13 id 0 time 11.32ms 44 bytes from 00:1A:7D:DA:71:13 id 1 time 66.06ms 44 bytes from 00:1A:7D:DA:71:13 id 2 time 19.84ms 44 bytes from 00:1A:7D:DA:71:13 id 3 time 52.38ms As of 05/2016, running the l2ping command on current Raspbian ends after about 5 seconds of pinging with a message: Send failed: Connection reset by peer Current thinking is that this is caused by a deliberate kernel timeout of L2CAP requests that don't result in a connection. The theory believed to be that to save battery life in real Bluetooth devices, if a connection isn't established in the timeout period, don't bother to keep trying and, presumably, waste power resources. The l2ping command is delivered as part of the "bluez" package. See also: • man(1) – l2ping


See also: • man(1) – rfcomm


One of the primary tools for working with Bluetooth is called bluetoothctl. Oddly, this command seems to have only the bare bones of a man page (shame). One should run bluetoothctl as root using: $ sudo bluetoothctl If you fail to run it as root, it will just silently sit there until you interrupt it. This command line tool has the following commands: • list – List available controllers • show [ctrl] – Controller information • select <ctrl> – Select default controller • devices – List available devices • paired-devices – List paired devices • power <on/off> – Set controller power • pairable <on/off> – Set controller pairable mode • discoverable <on/off> – Set controller discoverable mode • agent <on/off/capability> – Enable/disable agent with given capability • default-agent – Set agent as the default one • scan <on/off> – Scan for devices • info <dev> – Device information • pair <dev> – Pair with device • trust <dev> – Trust device • untrust <dev> – Untrust device • block <dev> – Block device • unblock <dev> – Unblock device • remove <dev> – Remove device • connect <dev> – Connect device • disconnect <dev> – Disconnect device • version – Display version • quit – Quit program See also:


As mentioned, HCI is the "Host-Controller Interface" which is the layer of communication between the higher level protocols of bluetooth and the bluetooth lower level controller. The "hciconfig" command allows us to execute commands through this logical interface. Running hciconfig by itself will list all the bluetooth devices found on the computer: $ hciconfig hci0: Type: BR/EDR Bus: UART BD Address: B8:27:EB:62:03:9F ACL MTU: 1021:8 SCO MTU: 64:1 UP RUNNING PSCAN RX bytes:19100 acl:150 sco:0 events:457 errors:0 TX bytes:7952 acl:150 sco:0 commands:184 errors:0 Each bluetooth device has a logical identifier of the form "hciX" where the devices are numbered 0, 1, 2 … etc. To refer to a specific device, most of the commands that we issue through hciconfig will take the hciX device identifier to target the correct instance. Some of the more interesting commands we can perform with hciconfig include: • Getting and setting the devices display name property • Enabling/disabling page support • Enabling/disabling scan inquiry support The hciconfig command is supplied as part of the "bluez" package. Some useful commands include: Start LE broadcasting connectable undirected advertising $ sudo hciconfig hci0 leadv 0 Start LE broadcasting non-connectable undirected advertising $ sudo hciconfig hci0 leadv 3 Stop LE broadcasting $ sudo hciconfig hci0 noleadv See also: • hcitool • man(8) – hciconfig


This tool is installed through: $ sudo apt-get install bluez-hcidump The tool appears to dump the commands sent through the host/controller interface. Try running with: $ sudo hcidump -x -R to see low-level data.


The hcitool is delivered as part of the "bluez" package on Linux. We can issue raw commands through the HCI using: hcitool cmd <OGF> <OCF> Where the combination of the two bytes <OGF> and <OCF> define the command to run. For LE controller commands, the <OGF> is 0x08. To scan for bluetooth LE devices, use: $ sudo hcitool lescan We can start advertising packets. See BT Spec 4.2 Vol 2, Part E 7.8.7 Using: $ sudo hcitool cmd 0x08 0x0008 <Length> <Content> $ hciconfig hci0 leadv 0 See also: • man(1) – hcitoo l • hciconfig


Interact with a BLE device at the GATT level. In order to interact with a BLE device at the gatt level, we need its device address. Using "hictool lescan" is a good way to get the address. Typically the program is run with: $ sudo gatttool --device=<Address> --interactive This will return a command prompt that starts with the partner device address: [FF:FF:45:19:14:80][LE]> The sub-commands available to us include: connect [address [address type]] Connect to a remote device disconnect Disconnect from a remote device primary [UUID] Primary Service Discovery included [start hnd [end hnd]] Find Included Services characteristics [start hnd [end hnd [UUID]]] Characteristics Discovery char-desc [start hnd] [end hnd] Characteristics Descriptor Discovery char-read-hnd <handle> Characteristics Value/Descriptor Read by handle char-read-uuid <UUID> [start hnd] [end hnd] Characteristics Value/Descriptor Read by UUID char-write-req <handle> <new value> Characteristic Value Write (Write Request) char-write-cmd <handle> <new value> Characteristic Value Write (No response) sec-level [low | medium | high] Set security level. Default: low mtu <value> Exchange MTU for GATT/ATT Once connected, we can interrogate the device about its primary function by running the "primary" command: [FF:FF:45:19:14:80][LE]> primary attr handle: 0x0001, end grp handle: 0x0005 uuid: 00001800-0000-1000-8000-00805f9b34fb attr handle: 0x0006, end grp handle: 0x0008 uuid: 0000180f-0000-1000-8000-00805f9b34fb attr handle: 0x0009, end grp handle: 0x000b uuid: 00001802-0000-1000-8000-00805f9b34fb attr handle: 0x000c, end grp handle: 0x000e uuid: 0000ffe0-0000-1000-8000-00805f9b34fb Notice specifically the UUIDs. These correspond to the assigned numbers of the GATT specifications. Contrast this with the output of "bluetoothctl info" which shows the following: [bluetooth]# info FF:FF:45:19:14:80 Device FF:FF:45:19:14:80 Alias: FF-FF-45-19-14-80 Appearance: 0x03c1 Icon: input-keyboard Paired: no Trusted: no Blocked: no Connected: yes LegacyPairing: no UUID: Generic Access Profile (00001800-0000-1000-8000-00805f9b34fb) UUID: Immediate Alert (00001802-0000-1000-8000-00805f9b34fb) UUID: Battery Service (0000180f-0000-1000-8000-00805f9b34fb) UUID: Unknown (0000ffe0-0000-1000-8000-00805f9b34fb) For example, 0x...1802… is the Immediate Alert service. Looking back at the gatttool output, for each of the services, we see a "handle range". This describes the handles to the characteristics offered by that service. From the handles, we can ask the device what characteristics these represent: [FF:FF:45:19:14:80][LE]> char-desc 0x0009 0x000b handle: 0x0009, uuid: 00002800-0000-1000-8000-00805f9b34fb handle: 0x000a, uuid: 00002803-0000-1000-8000-00805f9b34fb handle: 0x000b, uuid: 00002a06-0000-1000-8000-00805f9b34fb The UUIDs of the declarations and characteristics can then be examine in the assignednumbers lists. For example • 0x...2800… – GATT Primary Service Declaration • 0x...2803 … – GATT Characteristic Declaration • 0x...2A06... – Alert Level See also: • Bluetooth GATT • man(1) – gatttoo l

Bluetooth examples

The iTag peripheral

The iTag is a cheap little thingy ($2-$4 on eBay) that is a bluetooth device. Its purpose is to form a connection with a bluetooth master. If the connection is subsequently lost, the device starts beeping. In addition, the device can receive a "ping" from the master to instruct it to immediately start beeping. In all these cases, it acts as a useful device to help you locate it should it get lost. Now imagine connecting it to your keys, pet, kid or other loose-able thing and now we have a potential of either being notified that it is out of range or else it will beep to say "help me". This makes a great device for testing the ESP32's ability to work as a BLE master. If we run a BLE scan and listen for advertising packets, we will find that it shows up. From there we can get its bluetooth address. For example, when I ran an ESP32 to look for devices, the ESP32 found my tag with the address "FF:FF:45:14:80". Once the ESP32 found the address, I was the able to issue an open request to it which succeeded. Once I had an open connection, I issued a search request upon it and four services were returned. These were: • UUID: 0x1800 • UUID: 0x1802 • UUID: 0x180f • UUID: 0xffe0 Since these were 16bit UUIDs, that is the indication that they are specified by the bluetooth special interest group (SIG). A search at the GATT services web page found the first three: • UUID: 0x1800 – Generic Access • UUID: 0x1802 – Immediate Alert • UUID: 0x180f – Battery Service Great … this is starting to make sense. Now we can drill down into the characteristics for each service. From the bluetooth specs, the characteristics possible are: • UUID: 0x1800 – Generic Access ◦ UUID: 0x2a00 – Device Name ◦ UUID: 0x2a01 – Appearance ◦ UUID: 0x2a02 – Peripheral Privacy Flag ◦ UUID: 0x2a03 – Reconnection Address ◦ UUID: 0x2a04 – Peripheral Preferred Connection Parameters • UUID: 0x1802 – Immediate Alert ◦ UUID: 0x2a06 – Alert Level • UUID: 0x180f – Battery Service ◦ UUID: 0x2a19 – Battery Level When we actually performed a characteristics query, what was returned were: • UUID: 0x1800 – Generic Access ◦ UUID: 0x2a00 – Device Name ◦ UUID: 0x2a01 – Appearance • UUID: 0x1802 – Immediate Alert ◦ UUID: 0x2a06 – Alert Level • UUID: 0x180f – Battery Service ◦ UUID: 0x2a19 – Battery Level Close enough to what we expected.

Smart Watch / The TW64 Band

The TW64 watch/band can be found on eBay for about $9. Performing an eBay search using "TW64" will turn up many. We start by running a BLE scan: $ sudo hcitool lescan Which came back with: LE Scan ... A4:C1:38:77:1A:19 KeepBand A4:C1:38:77:1A:19 (unknown) And now we know the device address. Of course, each instance will vary. next we can connect to the device and ask it about itself: $ sudo gatttool --device=A4:C1:38:77:1A:19 --interactive > connect > primary attr handle: 0x0001, end grp handle: 0x0007 uuid: 00001800-0000-1000-8000-00805f9b34fb attr handle: 0x0008, end grp handle: 0x000b uuid: 00010203-0405-0607-0809-0a0b0c0d1911 attr handle: 0x000c, end grp handle: 0x0011 uuid: 66880000-0000-1000-8000-008012563489 and similarly, we can also run: $ sudo bluetoothctl

  1. info A4:C1:38:77:1A:19

Device A4:C1:38:77:1A:19 Name: KeepBand Alias: KeepBand Appearance: 0x0180 Paired: no Trusted: no Blocked: no Connected: no LegacyPairing: no UUID: Human Interface Device (00001812-0000-1000-8000-00805f9b34fb) UUID: Battery Service (0000180f-0000-1000-8000-00805f9b34fb) Interestingly, notice the distinction in services offered. A search at the GATT services web page found: • UUID: 0x1800 – Generic Access • UUID: 0x180f – Battery Service • UUID: 0x1812 – Human Interface Device Again using gattool, we can ask for the description of characteristics: char-desc handle: 0x0001, uuid: 00002800-0000-1000-8000-00805f9b34fb handle: 0x0002, uuid: 00002803-0000-1000-8000-00805f9b34fb handle: 0x0003, uuid: 00002a00-0000-1000-8000-00805f9b34fb handle: 0x0004, uuid: 00002803-0000-1000-8000-00805f9b34fb handle: 0x0005, uuid: 00002a01-0000-1000-8000-00805f9b34fb handle: 0x0006, uuid: 00002803-0000-1000-8000-00805f9b34fb handle: 0x0007, uuid: 00002a04-0000-1000-8000-00805f9b34fb handle: 0x0008, uuid: 00002800-0000-1000-8000-00805f9b34fb handle: 0x0009, uuid: 00002803-0000-1000-8000-00805f9b34fb handle: 0x000a, uuid: 00010203-0405-0607-0809-0a0b0c0d2b12 handle: 0x000b, uuid: 00002901-0000-1000-8000-00805f9b34fb handle: 0x000c, uuid: 00002800-0000-1000-8000-00805f9b34fb handle: 0x000d, uuid: 00002803-0000-1000-8000-00805f9b34fb handle: 0x000e, uuid: 66880001-0000-1000-8000-008012563489 handle: 0x000f, uuid: 00002803-0000-1000-8000-00805f9b34fb handle: 0x0010, uuid: 66880002-0000-1000-8000-008012563489 handle: 0x0011, uuid: 00002902-0000-1000-8000-00805f9b34fb