As we learned in a previous article, Bluetooth Low Energy (LE) data transmission can be divided into two categories:
- Connection-oriented.
- Connectionless.
Let’s examine a case of connectionless communication—namely, a Bluetooth LE device that broadcasts custom data.
This isn’t uncommon in the real world. Bluetooth LE beacons, for example, are used in stores, museums, and airports to offer location-based services. They function by broadcasting static custom data containing unique codes to nearby devices such as our phones.
There’s also dynamic custom data, which is what an environmental sensor might use to report real-time changes in the humidity level of an agricultural facility. Embedding custom data provides a platform to communicate proprietary information or distinct functionalities not addressed by standard data types.
In this tutorial, we’ll use an nRF52 DK (development kit) from Nordic Semiconductor and the nRF Connect software development kit for VS Code to create a beacon that broadcasts custom data. Like many other Bluetooth LE engineers and enthusiasts, I find that the nRF52 series offers the capability to design and test Bluetooth LE applications with relative ease.
At first, our beacon will broadcast static custom data. Then, we’ll tweak it so that it broadcasts custom data. Before we can do any of that, however, we need to briefly go over how our data is structured.
Advertising Data Structure and Type
When a Bluetooth LE device is broadcasting, it sends out advertising packets (and sometimes scans for response packets). The information in each packet is organized in a specific way. Figure 1 provides a visual representation of this organization.
Figure 1. Breakdown of Bluetooth LE legacy advertisement packets. Image used courtesy of MDPI
As an aside, the byte maximums given in this figure are for legacy, not extended, advertising mode. That’s fine for our purposes—we’ll be using legacy advertising mode throughout the article.
In the bottom two sections of Figure 1, we see that each advertising data (AD) packet contains one or more AD structures. Each structure, in turn, includes the following fields:
- Length: This takes up 1 byte and specifies the length of the subsequent data field, including the AD type but not including itself.
- AD type: This also takes up 1 byte. It determines the type of data that follows.
- AD data: This is the actual data content associated with the AD type. The length of this field is variable, but can be inferred from the ‘Length’ byte.
The AD type must come from the list of AD types defined in the Bluetooth Core Specification. We’ll be using the Manufacturer Specific Data type, which is designated as 0xFF. As its name implies, this AD type enables manufacturers to embed custom data in their advertisements.
With the Manufacturer Specific Data type, the initial two bytes of the AD data represent the Company ID. Company IDs are unique numbers assigned by the Bluetooth SIG to member companies upon request. After specifying the Company ID, manufacturers append custom data in any format they choose.
Recall that the entire legacy advertisement packet has a maximum length of 31 bytes. When using Manufacturer Specific Data, the packet requires:
- 1 byte to specify length.
- 1 byte to specify data type.
- 2 bytes for the company ID.
That leaves you with 27 bytes per packet for custom data.
And with that, we’re ready to move on and put our knowledge to use!
Static Data Use Case: Broadcasting Device Status and Message
Imagine you wanted to use your nRF52 DK as a rudimentary status beacon for a room or workstation. It could indicate if the workstation is occupied or free. We’ll do this with short, custom messages: “Meeting in Progress” when occupied and “Free to Use” when not.
Step 1: Declare the Company ID
Since we’re using the nRF52 DK board from Nordic Semiconductor for educational purposes, we can use their Company Identifier:
#define COMPANY_ID 0x0059
Step 2: Declare the Structure for Your Custom Data
In this project, our custom data structure will contain:
- Company ID (2 bytes): We begin the broadcasted data with Nordic Semiconductor’s Company ID.
- Status (1 byte): This can indicate if a room is occupied (1) or free (0).
- Message (up to 24 bytes): A custom message such as “Meeting in Progress” or “Free to Use.”
The code snippet below shows the structure.
typedef struct adv_mfg_data { uint16_t company_id; // Nordic Semiconductor's ID uint8_t status; // Room status: 0 for free, 1 for occupied char message[24]; // Custom message } room_status_data_t;
Step 3: Include the Manufacturer Specific Data in the Scan Response Advertising Packet
With the nRF Connect SDK, you can include this custom data in your advertisement packet using the BT_DATA() macro.
static const struct bt_data sd[] = { BT_DATA(BT_DATA_MANUFACTURER_DATA, (unsigned char *)&room_data, sizeof(room_data)), };
The complete code is included at the end of the article as Code Appendix A.
Dynamic Data Use Case: Broadcasting Status Changes in Real Time
In the prior example, we showcased broadcasting static custom data—specifically, the room’s status and its associated message. But what if our application demands that we broadcast changing data? This is where sending dynamic data comes in handy. It’s especially useful when developing sensor broadcasters or devices that reflect immediate status changes.
To demonstrate, let’s upgrade our basic room status beacon so that it makes use of the built-in button on the nRF52 board. Press the button, and the device will say “Meeting in Progress”. Let go, and it switches to “Free to Use.” The nRF52’s built-in LED will light up for “Meeting in Progress” and turn off for “Free to use.”
To broadcast dynamic data using the nRF Connect SDK, we use the bt_le_adv_update_data() function, which allows us to update the advertisement data while advertising is ongoing. This enables the broadcasted data to reflect changes in real time.
The bt_le_adv_update_data() function can be seen in the code snippet below.
static void button_changed(uint32_t button_state, uint32_t has_changed) { if (has_changed & button_state & USER_BUTTON) { room_data.status = 1; strncpy(room_data.message, "Meeting in Progress", sizeof(room_data.message)); bt_le_adv_update_data(ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd)); dk_set_led_on(DK_LED1); } else { room_data.status = 0; strncpy(room_data.message, "Free to Use", sizeof(room_data.message)); bt_le_adv_update_data(ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd)); dk_set_led_off(DK_LED1); } }
You can view all of the code for this version of the beacon in Code Appendix B.
Viewing the Results
Finally, it’s time to see if the code works. Figure 2 shows the output from the nRF Connect for VS Code.
Figure 2. [Click to enlarge] The beacon begins operation. Image used courtesy of Nthatisi Hlapisi
As you can see, the beacon has successfully started broadcasting.
Figures 3 and 4 are images from my nRF Connect Mobile App. Figure 3 shows how the message received by my phone changes when the button on the nRF52 is pressed and released.
Figure 3. The message broadcast by the room beacon when the nRF52 DK’s button is pressed (left) and released (right). Image used courtesy of Nthatisi Hlapisi
In Figure 4, we see the data instead of the custom text. Note how the Type 0xFF (Manufacturer Specific Data) values in the right-hand image match the values in the left-hand image, starting with Nordic Semiconductor’s Company ID.
Figure 4. Screen captures illustrating the custom data broadcast by the beacon. Image used courtesy of Nthatisi Hlapisi
This is only a simple example of what custom data can be used for. I hope you’ve found it instructive. If you’d like to build your own version of this project, all of the necessary code is included in the two appendices below.
Featured image used courtesy of Adobe Stock