Service Registration

Attribute Set

The NimBLE host uses a table-based design for GATT server configuration. The set of supported attributes are expressed as a series of tables that resides in your C code. When possible, we recommend using a single monolithic table, as it results in code that is simpler and less error prone. Multiple tables can be used if it is impractical for the entire attribute set to live in one place in your code.

bleprph uses a single attribute table located in the gatt_svr.c file, so let’s take a look at that now. The attribute table is called gatt_svr_svcs; here are the first several lines from this table:

static const struct ble_gatt_svc_def gatt_svr_svcs[] = {
    {
        /*** Service: Security test. */
        .type               = BLE_GATT_SVC_TYPE_PRIMARY,
        .uuid               = &gatt_svr_svc_sec_test_uuid.u,
        .characteristics    = (struct ble_gatt_chr_def[]) { {
            /*** Characteristic: Random number generator. */
            .uuid               = &gatt_svr_chr_sec_test_rand_uuid.u,
            .access_cb          = gatt_svr_chr_access_sec_test,
            .flags              = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC,
        }, {
            /*** Characteristic: Static value. */
            .uuid               = gatt_svr_chr_sec_test_static_uuid.u,
            .access_cb          = gatt_svr_chr_access_sec_test,
            .flags              = BLE_GATT_CHR_F_READ |
                                  BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_ENC,
        }, {
    // [...]

As you can see, the table is an array of service definitions ( struct ble_gatt_svc_def). This code excerpt contains a small part of the GAP service. The GAP service exposes generic information about a device, such as its name and appearance. Support for the GAP service is mandatory for all BLE peripherals. Let’s now consider the contents of this table in more detail.

A service definition consists of the following fields:

Field

Meaning

Notes

type

Specifies whether this is a primary or secondary service.

Secondar y services are not very common. When in doubt, specify BLE_GA TT_SVC_TYPE_P RIMARY for new services .

uuid128

The UUID of this service.

If the service has a 16-bit UUID, you can convert it to its correspo nding 128-bit UUID with the BLE_UU ID16() macro.

characte ristics

The array of characteri stics that belong to this service.

A service is little more than a container of characteristics; the characteristics themselves are where the real action happens. A characteristic definition consists of the following fields:

Field

Meaning

Notes

uuid128

The UUID of this characteri stic.

If the characte ristic has a 16-bit UUID, you can convert it to its correspo nding 128-bit UUID with the BLE_UU ID16() macro.

access_ cb

A callback function that gets executed whenever a peer device accesses this characteri stic.

For reads: this function generate s the value that gets sent back to the peer.* For writes:* this function receives the written value as an argument .

flags

Indicates which operations are permitted for this characteri stic. The NimBLE stack responds negatively when a peer attempts an unsupporte d operation.

The full list of flags can be found under ble_ga tt_chr_f lags in net/nim ble/host /include /host/bl e_gatt. h .

The access callback is what implements the characteristic’s behavior. Access callbacks are described in detail in the next section: BLE Peripheral - Characteristic Access.

The service definition array and each characteristic definition array is terminated with an empty entry, represented with a 0. The below code listing shows the last service in the array, including terminating zeros for the characteristic array and service array.

{
    /*** Service: Security test. */
    .type = BLE_GATT_SVC_TYPE_PRIMARY,
    .uuid = &gatt_svr_svc_sec_test_uuid.u,
    .characteristics = (struct ble_gatt_chr_def[]) { {
        /*** Characteristic: Random number generator. */
        .uuid = &gatt_svr_chr_sec_test_rand_uuid.u,
        .access_cb = gatt_svr_chr_access_sec_test,
        .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC,
    }, {
        /*** Characteristic: Static value. */
        .uuid = &gatt_svr_chr_sec_test_static_uuid.u,
        .access_cb = gatt_svr_chr_access_sec_test,
        .flags = BLE_GATT_CHR_F_READ |
                 BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_ENC,
    }, {
        0, /* No more characteristics in this service. */
    } },
},

{
    0, /* No more services. */
},

Registration function

After you have created your service table, your app needs to register it with the NimBLE stack. This is done by calling the following function:

int
ble_gatts_add_svcs(const struct ble_gatt_svc_def *svcs)

where svcs is table of services to register.

The ble_gatts_add_svcs() function returns 0 on success, or a BLE_HS_E[…] error code on failure.

More detailed information about the registration callback function can be found in the BLE User Guide.

The bleprph app registers its services as follows:

rc = ble_gatts_add_svcs(gatt_svr_svcs);
assert(rc == 0);

which adds services to registration queue. On startup NimBLE host automatically calls ble_gatts_start() function which makes all registered serivices available to peers.

Descriptors and Included Services

Your peripheral can also expose descriptors and included services. These are less common, so they are not covered in this tutorial. For more information, see the BLE User Guide.