This is an old revision of the document!
Table of Contents
Zephyr BLE peripheral
In this example, we will observe BLE behaviour in peripheral (server) mode. In Zephyr OS there are some software examples of different BLE devices. One of them is “peripheral_ht” available at the path:
C:\Users\...\.platformio\packages\framework-zephyr\samples\bluetooth\peripheral_ht
This example uses the following services:
- Battery service (BAS)
- Device information service
- Health thermometer service
Battery service allows reading the characteristic with simulated data stored in the variable that is decremented every second. The device information service presents two characteristics which allow reading two text strings. The health thermometer service reads temperature data from the nRF52840 built-in temperature sensor and sends measurements to the client while connected. All services are presented in the advertising frame created with an “ad” structure similar to the one from the beacon example.
static const struct bt_data ad[] = {
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
BT_DATA_BYTES(BT_DATA_UUID16_ALL,
BT_UUID_16_ENCODE(BT_UUID_HTS_VAL),
BT_UUID_16_ENCODE(BT_UUID_DIS_VAL),
BT_UUID_16_ENCODE(BT_UUID_BAS_VAL)),
};
Services are encoded as 16-bit UUID values which can be found in Bluetooth SiG documentation 1).
| UUID constant | UUID value | UUID name |
|---|---|---|
| BT_UUID_HTS_VAL | 0x1809 | Health Thermometer |
| BT_UUID_DIS_VAL | 0x180a | Device Information |
| BT_UUID_BAS_VAL | 0x180f | Battery |
Advertising is started with the “bt_le_adv_start()” function call:
err = bt_le_adv_start(BT_LE_ADV_CONN_NAME, ad, ARRAY_SIZE(ad), NULL, 0);
if (err) {
printk("Advertising failed to start (err %d)\n", err);
return;
}
Notice that in this example there is no additional scan response information provided while calling the advertising start function. Modification of parameters of this function results in changes in advertising information only. If you want to add another service it must be implemented also in another part of the software.
Battery service
Battery service is the standard service intended to return the battery level expressed in percentages. The battery service is fully implemented in ZephyrOS, so we need to activate it only. It is done in “prj.conf” file with the line:
CONFIG_BT_BAS=y
Simple functions:
uint8_t battery_level = bt_bas_get_battery_level(); bt_bas_set_battery_level(battery_level);
can be used to read and set the value of the characteristic responsible for the current battery percentage.
Device Information Service
Device Information (DIS) Service is enabled with the configuration option:
CONFIG_BT_DIS=y
It is implemented in Zephyr OS and automatically handled. By default, it has two active characteristics: Model Number String, and Manufacturer Number String. Default strings can be replaced by setting the options in prj.conf:
CONFIG_BT_DIS_MODEL="My model" CONFIG_BT_DIS_MANUF="Producer"
Health thermometer service
In the example, there is also a health thermometer service that is not fully implemented in Zephyr OS as a battery service is. The part of the software responsible for the definition, and for handling of this service is written in the “hts.c” file. The built-in thermometer is not enabled by default and is not enabled in the example files. It can be switched on in the prj.conf file by setting the option:
CONFIG_TEMP_NRF5=y
And now let us explain how the service is handled. To define the service the macro “BT_GATT_SERVICE_DEFINE” is used:
/* Health Thermometer Service Declaration */ BT_GATT_SERVICE_DEFINE(hts_svc, BT_GATT_PRIMARY_SERVICE(BT_UUID_HTS), BT_GATT_CHARACTERISTIC(BT_UUID_HTS_MEASUREMENT, BT_GATT_CHRC_INDICATE, BT_GATT_PERM_NONE, NULL, NULL, NULL), BT_GATT_CCC(htmc_ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), /* more optional Characteristics */ );
BT_GATT_CHARACTERISTIC macro has the following list of parameters
BT_GATT_CHARACTERISTIC(_uuid,_props,_perm,_read,_write,_user_data)
The meaning of parameters are as follows:
- _uuid – Characteristic attribute uuid.
- _props – Characteristic attribute properties (what appears as available for the client: read, write, indicate etc.)
- _perm – Characteristic Attribute access permissions (if the client can really read or write the characteristic data.)
- _read – Characteristic Attribute read callback (address of the function called while client wants to read the data.)
- _write – Characteristic Attribute write callback (address of the write function.)
- _user_data – Characteristic Attribute user data.
The indication mechanism allows the server to send data to the client only when needed (for example periodically or on value change). The client must enable indication by setting the descriptor with UUID 0x2902 to 0x0002. This can be done in the nRF Connect application by tapping the two arrows symbol, as in figure 1. Three-arrows symbol visible in BAS service means notification, as in figure 2, which can be enabled by setting the same descriptor to 0x0001.
The notification value is updated by “bt_gatt_indicate(NULL, &ind_params)” function call, where the structure “ind_params” holds the attributes of the health thermometer service, pointers to callback functions executed at the beginning and the end of the indication, and a new value to be sent to the client. The attributes “ind_params.attr” that hold the UUID of the service connect this notification structure to the chosen service (here “hts_svc”).
Adding the possibility of reading the characteristic
In the example, the health temperature service allows an indicating mode of data reading only, enabled with the flag “BT_GATT_CHRC_INDICATE”. To enable reading the value from the characteristic there is a need to do four things:
- add property BT_GATT_CHRC_READ (if you want to allow both indicate and read you need to “or” both flags with the “pipe” operator “|”)
- modify permissions with BT_GATT_PERM_READ
- write the callback function that calls bt_gatt_attr_read() (example function in the following code)
- register callback function within the BT_GATT_CHARACTERISTIC macro using the “_read” parameter.
An example of the callback function which simulates the thermometer value can look like this:
static ssize_t read_ht(struct bt_conn *conn,
const struct bt_gatt_attr *attr, void *buf,
uint16_t len, uint16_t offset)
{
uint8_t lvl8 = 36;
return bt_gatt_attr_read(conn, attr, buf, len, offset, &lvl8, sizeof(lvl8));
}
Tasks to do during the laboratory
- Import the peripheral_ht example.
- Connect to the exposed server with the nRF Connect application (or similar)
- Observe notification and indication mechanisms in BAS and health temperature services
- Modify the program to enable the reading possibility of the health temperature (value can be simulated)
