GATT — Usage Scenarios#
WiSeConnect BLE example projects under examples/snippets/ble/<example_folder>/ illustrate GATT server and client flows. Each scenario below states when to use it and gives a compact implementation example: #include lines as in the SDK, a one-line // caption, then the API sequence. Non-essential branches are folded to // .... Full sources live under wiseconnect/examples/snippets/ble/ relative to the WiSeConnect SDK content root; the same tree is published in the Silicon Labs wiseconnect GitHub repository (WiseConnect SDK).
Related: BLE usage scenarios hub, GATT overview, BLE initialization and configuration, Debugging and error handling.
Register GATT Callbacks#
When to Use#
After wireless/BLE stack initialization, register GATT event handlers before calling GATT procedures that return asynchronous responses (discovery, read/write, MTU, notifications).
Pass
NULLfor slots your application does not use.
Implementation Example#
rsi_ble_gatt_register_callbacks() takes twenty-three parameters (WiSeConnect rsi_ble_apis.h). Unused parameters are NULL. A non-NULL argument must be a function pointer type-compatible with that position’s rsi_ble_on_*_t typedef (parameter and return types only; the function identifier is not specified by the API). Snippet identifiers follow SDK examples and are illustrative. End-of-line comments pair role or feature with NULL if … (for example GATT client — NULL if server-only or unused).
#include "rsi_ble_apis.h"
rsi_ble_gatt_register_callbacks(
rsi_ble_on_profiles_list_resp, // GATT client — NULL if server-only or unused
rsi_ble_on_profile_resp, // GATT client — NULL if server-only or unused
rsi_ble_on_char_services_resp, // GATT client — NULL if server-only or unused
rsi_ble_on_inc_services_resp, // GATT client — NULL if server-only or unused
rsi_ble_on_att_desc_resp, // GATT client — NULL if server-only or unused
rsi_ble_on_read_resp, // GATT client — NULL if server-only or unused
rsi_ble_on_write_resp, // GATT client — NULL if server-only or unused
rsi_ble_on_gatt_write_event, // GATT server or client — NULL if unused
rsi_ble_on_gatt_prepare_write_event, // GATT server — peer long write — NULL if client-only or unused
rsi_ble_on_execute_write_event, // GATT server — peer long write — NULL if client-only or unused
rsi_ble_on_read_req_event, // GATT server (host ATT) — NULL if client-only or unused
rsi_ble_on_mtu_event, // MTU — client or server — NULL if firmware MTU or unused
rsi_ble_on_gatt_error_resp, // GATT error — client or server — NULL if unused
rsi_ble_on_gatt_desc_val_event, // GATT client — NULL if server-only or unused
rsi_ble_on_profiles_list_event, // GATT client — NULL if server-only or unused
rsi_ble_on_profile_by_uuid_event, // GATT client — NULL if server-only or unused
rsi_ble_on_read_by_char_services_event, // GATT client — NULL if server-only or unused
rsi_ble_on_read_by_inc_services_event, // GATT client — NULL if server-only or unused
rsi_ble_on_read_att_value_event, // GATT client — NULL if server-only or unused
rsi_ble_on_read_resp_event, // GATT client — NULL if server-only or unused
rsi_ble_on_write_resp_event, // GATT client — NULL if server-only or unused
rsi_ble_on_indicate_confirmation_event, // GATT server — NULL if client-only or unused
rsi_ble_on_prepare_write_resp_event); // GATT client — long write — NULL if server-only or unusedGATT client vs GATT server examples register different non-NULL slots (for example ble_heart_rate_profile uses rsi_ble_on_gatt_write_event and discovery callbacks on the client build). SDK projects often use longer internal function names but the same slot order.
Note: Parameter order matches the WiSeConnect API. See Register Callbacks and Callbacks Declarations. Use
rsi_ble_gatt_extended_register_callbacks()when you need extended callbacks (for example MTU exchange information). Pairing flows often addrsi_ble_smp_register_callbacks()in the same initialization sequence.
Typical Use Case 1: GATT Server — Add Services and Characteristics#
When to Use#
The device exposes services and characteristics to a connected central (peripheral GATT server is the common case).
You add a primary service, characteristics (including notify or indicate), and update values or send notifications after the peer enables the CCCD.
Implementation Example#
Step 1: Add a Service and Attributes#
The Heart Rate Service example builds the database with rsi_ble_add_service(), rsi_ble_add_char_serv_att(), and rsi_ble_add_char_val_att(). The sample uses notify on the measurement characteristic; the same call sequence applies for read, write, and indicate characteristics: choose the RSI_BLE_ATT_PROPERTY_* bits (and optional OR combinations) for the declaration and value attributes. Helper patterns in the same file add the CCCD when notify or indicate is in the property set.
#include "rsi_ble_apis.h"
// rsi_ble_heart_rate_add_new_serv() — ble_heart_rate_profile/app.c (excerpt)
new_uuid.size = 2;
new_uuid.val.val16 = RSI_BLE_HEART_RATE_SERVICE_UUID; // 0x180D
rsi_ble_add_service(new_uuid, &new_serv_resp);
// Same rsi_ble_add_char_serv_att / rsi_ble_add_char_val_att pattern for every characteristic:
// combine RSI_BLE_ATT_PROPERTY_* with bitwise OR as needed (rsi_ble_apis.h).
// e.g. RSI_BLE_ATT_PROPERTY_READ | RSI_BLE_ATT_PROPERTY_WRITE
// e.g. RSI_BLE_ATT_PROPERTY_READ | RSI_BLE_ATT_PROPERTY_INDICATE
new_uuid.val.val16 = RSI_BLE_HEART_RATE_MEASUREMENT_UUID;
rsi_ble_add_char_serv_att(new_serv_resp.serv_handler,
new_serv_resp.start_handle + 1,
RSI_BLE_ATT_PROPERTY_NOTIFY, // or READ, WRITE, INDICATE, or combined
new_serv_resp.start_handle + 2,
new_uuid);
rsi_ble_measurement_hndl = new_serv_resp.start_handle + 2;
rsi_ble_add_char_val_att(new_serv_resp.serv_handler,
new_serv_resp.start_handle + 2,
new_uuid,
RSI_BLE_ATT_PROPERTY_NOTIFY, // same mask as declaration
(uint8_t *)&rate,
sizeof(rate));Step 2: Enable Path and Value Updates#
When the central writes 0x0001 to the CCCD, the sample sets notify_start in rsi_ble_on_gatt_write_event and drives RSI_BLE_GATT_SEND_DATA. The application updates the measurement buffer with rsi_ble_set_local_att_value() while notifications are enabled (see Characteristic Notifications and Indications for rsi_ble_notify_value() / rsi_ble_indicate_value() where you send explicit notify/indicate).
// ble_heart_rate_profile/app.c — RSI_BLE_GATT_SEND_DATA (excerpt)
len = heartratefun(rate, (uint8_t *)data);
status = rsi_ble_set_local_att_value(rsi_ble_measurement_hndl, len, (uint8_t *)data);Step 3: Read, Write, Notify, and Indicate (Server)#
The following subsections summarize firmware-maintained vs host-maintained paths; see GATT server for full callback and response details. Snippets assume #include "rsi_ble_apis.h".
Read#
With firmware-maintained attributes, the stack typically satisfies peer read requests from values you set with rsi_ble_set_local_att_value() (and related add APIs). With host-maintained attributes (ATT_MAINTAIN_IN_HOST / opermode), handle rsi_ble_on_read_req_event, map fields from rsi_ble_read_req_t, and respond with rsi_ble_gatt_read_response(). See Creating a GATT Server and Characteristic Properties.
// rsi_ble_on_read_req_event — host-maintained ATT (dev_addr, handle, offset, length, p_data from rsi_ble_read_req_t)
status = rsi_ble_gatt_read_response(dev_addr,
0, /* read response: 0; read blob response: 1 */
handle,
offset,
length,
p_data);Write#
Peer writes to writable characteristics or the CCCD arrive on rsi_ble_on_gatt_write_event (for example the sample sets notify_start when the central enables notifications). For host-maintained services, send rsi_ble_gatt_write_response() after handling rsi_ble_event_write_t. For prepare write, respond with rsi_ble_gatt_prepare_write_response(); for execute write, use type 1 on rsi_ble_gatt_write_response() or complete through rsi_ble_on_execute_write_event. See GATT server.
// rsi_ble_on_gatt_write_event — host-maintained ATT after processing rsi_ble_event_write_t
status = rsi_ble_gatt_write_response(dev_addr, 0); /* 0 = write response; 1 = execute write response */
// rsi_ble_on_gatt_prepare_write_event (peer long write)
// status = rsi_ble_gatt_prepare_write_response(dev_addr, handle, offset, length, data);Notify#
After the peer enables the CCCD for notifications, send updates with rsi_ble_notify_value() (or update firmware-maintained values per your project pattern). See Sending Notifications.
status = rsi_ble_notify_value(remote_dev_addr, char_handle, data_len, data);Indicate#
Use rsi_ble_indicate_value() or rsi_ble_indicate_value_sync() when the characteristic supports indicate and the peer has enabled indications on the CCCD. Wait for rsi_ble_on_indicate_confirmation_event before sending the next indication (unless using the synchronous API). See Sending Indications.
status = rsi_ble_indicate_value(remote_dev_addr, char_handle, data_len, data);
// status = rsi_ble_indicate_value_sync(remote_dev_addr, char_handle, data_len, data);GATT client read/write/subscribe paths are covered in Typical Use Case 2 — Step 4.
SDK Example Project#
Goal | Example folder |
|---|---|
Heart Rate Service (server + optional client role) |
|
HID over GATT |
|
See also: GATT server, GAP — usage scenarios (connectable peripheral).
Typical Use Case 2: GATT Client — Discover, Read, Write, Subscribe#
When to Use#
After GAP connection, discover services and characteristics on the remote GATT server, then read/write attributes and subscribe through the CCCD (notifications or indications).
Use the same callback registration model for multiple connections as in
ble_multiconnection_gatt_test(per-connection state in the example’s connection buffer).
Implementation Example#
Step 1: Primary Service by UUID#
After connection, request the Heart Rate Service with rsi_ble_get_profile_async(). Responses arrive on the registered profile callback; then request characteristics with rsi_ble_get_char_services_async().
#include "rsi_ble_apis.h"
// ble_heart_rate_profile/app.c — CLIENT, after connection (excerpt)
service_uuid.size = 2;
service_uuid.val.val16 = RSI_BLE_HEART_RATE_SERVICE_UUID;
status = rsi_ble_get_profile_async(conn_event_to_app.dev_addr, service_uuid, NULL);
// ... on profile response event:
status = rsi_ble_get_char_services_async(conn_event_to_app.dev_addr,
*(uint16_t *)profiles_list.start_handle,
*(uint16_t *)profiles_list.end_handle,
NULL);Step 2: Descriptors and CCCD (Enable Notifications)#
Discover descriptors with rsi_ble_get_att_descriptors_async(), find handle 0x2902 (CCCD), then write 0x01 0x00 with rsi_ble_set_att_value_async() to enable notifications.
// ble_heart_rate_profile/app.c — RSI_BLE_GATT_CHAR_DESC_RESP_EVENT (excerpt)
if (attr_desc_list.att_desc[ix].att_type_uuid.val.val16 == 0x2902) {
data[0] = 0x01;
data[1] = 0x00;
status = rsi_ble_set_att_value_async(conn_event_to_app.dev_addr,
*((uint16_t *)attr_desc_list.att_desc[ix].handle),
2,
(uint8_t *)data);
}Step 3: Incoming Notifications#
Notification payloads are delivered through rsi_ble_on_gatt_write_event with the appropriate event id (see GATT client).
Despite the name, rsi_ble_on_gatt_write_event is not only for ATT Write requests. The WiSeConnect stack uses this registered callback for several GATT event kinds; on a GATT client, Handle Value Notification and Handle Value Indication PDUs from the peer server are reported here with RSI_BLE_EVENT_GATT_NOTIFICATION and RSI_BLE_EVENT_GATT_INDICATION (the formal parameter in the API is ble_on_gatt_event). On a GATT server, the same callback handles incoming writes (and related paths) from the peer client.
Step 4: Read, Write, and Indicate (Client)#
The following subsections follow Typical Use Case 2 discovery (Steps 1–2). Snippets assume #include "rsi_ble_apis.h". See GATT client for callback typedefs and response structures.
Read#
With a characteristic value handle from discovery, read the peer attribute using rsi_ble_get_att_value_async(), or rsi_ble_get_char_value_by_uuid_async() / rsi_ble_get_long_att_value_async() when the handle is unknown or the value is longer than one ATT PDU. Responses complete through the registered read callbacks. See Reading Characteristics.
status = rsi_ble_get_att_value_async(conn_event_to_app.dev_addr, char_value_handle, NULL);
// status = rsi_ble_get_char_value_by_uuid_async(/* ... */);
// status = rsi_ble_get_long_att_value_async(/* ... */);Write#
Write peer attributes (including the CCCD in Step 2) with rsi_ble_set_att_value_async() (write with response) or rsi_ble_set_att_cmd_async() (write without response). See Writing Characteristics.
status = rsi_ble_set_att_value_async(conn_event_to_app.dev_addr, att_handle, data_len, data);
// status = rsi_ble_set_att_cmd_async(conn_event_to_app.dev_addr, att_handle, data_len, data);Receive Notifications#
After the CCCD is 0x0001 (Step 2), the server pushes notifications; payloads arrive in rsi_ble_on_gatt_write_event with RSI_BLE_EVENT_GATT_NOTIFICATION (see Step 3). Parse rsi_ble_event_write_t (see GATT client).
void rsi_ble_on_gatt_write_event(uint16_t event_id, rsi_ble_event_write_t *p_evt)
{
if (event_id == RSI_BLE_EVENT_GATT_NOTIFICATION) {
uint16_t len = p_evt->length;
uint8_t *payload = p_evt->att_value;
/* application: use p_evt->handle and payload[0.. len-1] */
}
}Receive Indications and Confirm#
With CCCD 0x0002, indications use RSI_BLE_EVENT_GATT_INDICATION. When RSI_BLE_INDICATE_CONFIRMATION_FROM_HOST is enabled in opermode configuration, the client calls rsi_ble_indicate_confirm() after handling the indication. See Notifications and Indications.
void rsi_ble_on_gatt_write_event(uint16_t event_id, rsi_ble_event_write_t *p_evt)
{
if (event_id == RSI_BLE_EVENT_GATT_INDICATION) {
/* parse p_evt->handle, length, att_value; optionally rsi_ble_app_set_event(RSI_BLE_GATT_INDICATION_EVENT) */
#ifdef RSI_BLE_INDICATE_CONFIRMATION_FROM_HOST
status = rsi_ble_indicate_confirm(p_evt->dev_addr);
#endif
}
}GATT server read responses, incoming writes, notify, and indicate (including rsi_ble_on_indicate_confirmation_event on the server) are covered in Typical Use Case 1 — Step 3 and Characteristic Notifications and Indications.
SDK Example Project#
Goal | Example folder |
|---|---|
HRP central path (same project as server) |
|
Multiple connections + GATT |
|
Long attribute read |
|
See also: GATT client, Central role.
Typical Use Case 3: SIG and Custom Profiles#
When to Use#
You implement a Bluetooth SIG profile (fixed 16-bit service/characteristic UUIDs) or a custom profile (typically 128-bit UUIDs) by composing services and characteristics to match the profile specification.
The stack does not ship a turnkey profile binary: the application owns service layout and behavior using the same GATT APIs as in Typical Use Case 1 and 2.
Implementation Example#
Map SIG UUIDs from the adopted specification (for example 0x180D Heart Rate, 0x2A37 Heart Rate Measurement), or define 128-bit UUIDs for proprietary services. The ble_heart_rate_profile example is the reference for a SIG service layout on SiWx917; custom profiles follow the same rsi_ble_add_service() / rsi_ble_add_attribute() patterns with your UUIDs.
// ble_heart_rate_profile/app.c — profile UUID constants (excerpt)
#define RSI_BLE_HEART_RATE_SERVICE_UUID 0x180D
#define RSI_BLE_HEART_RATE_MEASUREMENT_UUID 0x2A37
#define RSI_BLE_SENSOR_LOCATION_UUID 0x2A38
#define RSI_BLE_HEART_RATE_CONTROL_POINT_UUID 0x2A39SDK Example Project#
Goal | Example folder |
|---|---|
SIG Heart Rate layout |
|
Other profile-oriented samples |
|
See also: Profiles.
Typical Use Case 4: MTU Exchange and DLE (Larger ATT and LL Payloads)#
When to Use#
ATT MTU exchange sets the maximum ATT payload per connection (
ATT_MTU = min(client Rx MTU, server Rx MTU)). Larger ATT MTU reduces the number of ATT PDUs for long reads, writes, prepares, and notifications. MTU is an ATT procedure (roles are independent of GAP central/peripheral).Data Length Extension (DLE) raises the link-layer Data PDU payload (up to 251 octets). Use DLE when you need higher LL throughput; combine with MTU exchange when application data is large at the ATT layer.
When
RSI_BLE_MTU_EXCHANGE_FROM_HOSTis set in the Network Processor (NWP) BLE feature configuration, the application drives MTU exchange and must registerrsi_ble_on_mtu_event_t(slot 12 inrsi_ble_gatt_register_callbacks()). When that macro is not set, firmware typically completes MTU exchange without the app path.As ATT Server, respond to the peer’s MTU request with
rsi_ble_mtu_exchange_resp()when host-side handling is enabled (see GATT client — MTU exchange). For exchange details (includingrsi_ble_gatt_extended_register_callbacks()/rsi_ble_on_mtu_exchange_info_t), use the same topic page.
Implementation Example#
Step 1: MTU Exchange (Host Path, Central Example)#
The ble_datalength project enables SL_SI91X_BLE_MTU_EXCHANGE_FROM_HOST when RSI_BLE_MTU_EXCHANGE_FROM_HOST is 1 in the project’s BLE config (for example rsi_ble_common_config.h / project ble_config.h pattern used with other snippets). It registers rsi_ble_on_mtu_event in rsi_ble_gatt_register_callbacks(); the callback raises RSI_BLE_MTU_EVENT for the application task.
#include "rsi_ble_apis.h"
// ble_datalength/app.c — MTU callback (excerpt)
static void rsi_ble_on_mtu_event(rsi_ble_event_mtu_t *rsi_ble_mtu)
{
UNUSED_PARAMETER(rsi_ble_mtu);
rsi_ble_app_set_event(RSI_BLE_MTU_EVENT);
}
// ble_datalength/app.c — rsi_ble_gatt_register_callbacks (excerpt): 12th parameter is MTU
rsi_ble_on_mtu_event,After the MTU procedure completes, the example continues with DLE in the same task path. Inspect rsi_ble_event_mtu_t in the callback when you need negotiated values.
Step 2: Data Length Extension (DLE)#
DLE is a link-layer / GAP concern (larger LL Data PDU), not an ATT procedure. The ble_datalength project chains it after RSI_BLE_MTU_EVENT: register rsi_ble_on_data_length_update in rsi_ble_gap_register_callbacks(), then call rsi_ble_read_max_data_len() and rsi_ble_set_data_len() with valid TX_LEN / TX_TIME. Full sequence, code excerpt, and rsi_ble_event_data_length_update_t / rsi_ble_on_data_length_update_t completion are documented in GAP — Typical Use Case 8: Data Length Extension (DLE) and Data length extension.
Note: MTU (ATT) and DLE (LL) are separate; negotiate both when you need large payloads end-to-end.
SDK Example Project#
Goal | Example folder |
|---|---|
MTU event + DLE (central) |
|
See also: GAP — DLE usage scenario, Data length extension, GATT client — MTU exchange, GATT overview (MTU and ATT payload context).
APIs Used in This Section#
Topic | APIs / symbols | Reference |
|---|---|---|
GATT — register callbacks |
| |
GATT — callback typedefs |
| |
GATT server — database |
| |
GATT server — notify / indicate / host ATT |
| |
GATT client — async procedures |
| |
GATT client — notifications / indications |
| |
GATT — MTU exchange |
| ATT_MAINTAIN_IN_HOST, Synchronous Client, Callbacks Declarations |
GAP — DLE (with MTU in |
|
In-guide: BLE API reference. SDK: Developing with WiSeConnect SDK.