GATT Server and Client Roles#
Bluetooth Low Energy is a powerful and complex technology, which is different from the classic Bluetooth with a predefined set of official profiles to choose from. Although Bluetooth Low Energy does have predefined (a.k.a. "adopted") profiles specified by the Bluetooth SIG, they are just the tip of the iceberg, a small subset of the functionality possible to achieve with BLE.
In many (or even most) cases, the best option is to create custom profile(s) for your application because it provides ultimate flexibility without an associated cost. In fact, it can be even easier than using one of the adopted profiles because you get to define exactly how everything works rather than conforming your application into something that is already defined. Also, because there is no official generic "serial port profile" in the BLE world, such as SPP in classic Bluetooth, sometimes a custom implementation is the only option to do what you need.
To have an effective custom implementation, it is important to understand how a BLE connection works, what roles are played by the devices involved, and how data is transferred from one device to the other over the air. Many terms are used, which are usually not interchangeable and mean different things: central, peripheral, client, server, advertise, scan, read, write, notify, and indicate. Understanding the terminology will make it easier to describe and build your BLE application.
This document covers the following:
Client devices access remote resources over a BLE link using the GATT protocol. Usually, the central is the client (but not necessarily).
Server devices have a local database and access control methods and provide resources to the remote client. Usually, the peripheral is the server (but not necessarily).
You can use read, write, notify, or indicate operations to move data between the client and the server.
Read and write operations are requested by the client and the server responds (or acknowledges).
Notify and indicate operations are enabled by the client but initiated by the server, providing a way to push data to the client.
Notifications are unacknowledged, while indications are acknowledged. Notifications are therefore faster but less reliable.
To learn about central and peripheral roles, see Central and Peripheral Roles.
GATT Server vs. GATT Client#
An important concept in BLE design is the difference between a GATT server and a GATT client (where GATT means Generic ATTribute profile). These roles are not mutually exclusive, though typically your device will only be a server or a client. Role(s) that your device takes depend on its intended functionality. This is a basic summary of functionalities:
GATT client - a device which accesses data on the remote GATT server via read, write, notify, or indicate operations
GATT server - a device which stores data locally and provides data access methods to a remote GATT client
Unlike the central/peripheral distinction defined under Connections, it is easy to see that one device might be both at the same time, based on how your application defines the data structure and flow for each side of the connection. While it is most common for the peripheral device to be the GATT server and the central device to be the GATT client, this is not required. The GATT functionality of a device is logically separate from the central/peripheral role. The central/peripheral roles control how the BLE radio connection is managed while the client/server roles are dictated by the storage and flow of data.
Most of the example projects in the SDK archive and online implement peripheral devices designed to be GATT servers. These are easy to test with our EFR Connect App (available for Android and iOS). However, there are also a few examples which implement the central end of the connection, designed to be GATT clients.
Receive vs. Transmit - Moving Data#
In BLE projects built using the Bluetooth SDK, the GATT structure can be configured using the built-in tool from Simplicity Studio, called the Bluetooth GATT Configurator. This can be found under the Configuration tools in the .slcp file. After you modify the GATT Configuration, the gatt_db.c/.h and the gatt.xml files are generated.
The structure and flow of data is always defined on the GATT server. The client uses whatever is exposed by the server.
If using the IAR Embedded Workbench, see Profile Toolkit Developer Guide.
A GATT database implements one or more profiles, which are made up of one or more services. Each service is made up of one or more characteristics. For example, in outline form:
You can implement as many profiles, services, and characteristics as you need. These may be entirely customized, in which case they would use your own 128-bit UUIDs generated by the GATT Configurator. On the other hand, you can create a project which implements adopted specifications by referencing the Bluetooth SIG's online definitions of profiles, services, and characteristics. If you do this, it usually makes the most sense to start at the profile level and drill down from there because the full list of characteristics includes many things that will undoubtedly be irrelevant to your design. You can combine these two as well, using some adopted profiles/services and some of your own proprietary ones.
Every BLE device acting as a GATT server must implement the official Generic Access service. This includes two mandatory characteristics, which are Device Name and Appearance. These are similar to the Friendly Name and Class of Device values used by classic Bluetooth. This is an example definition of an absolutely minimal GATT definition, as shown in the GATT Configurator:
Attributes and Characteristics#
You may occasionally hear or see the terms "attribute" and "characteristic" used interchangeably, and while this isn't entirely wrong, it isn't totally accurate and can be confusing. Remember that a service is made up of one or more characteristics. However, one single characteristic, generally the most specific level down to which we define our GATT structure, may be comprised of many different attributes.
Each attribute is given a unique numerical handle, which the GATT client may use to reference, access, and modify it. Every characteristic has one main attribute, which allows access to the value stored in the database for that characteristic. When you read about "writing a value to this characteristic" or "reading that characteristic's value," the read/write operations are done to the main data attribute.
Other related attributes are read-only, such as a Characteristic User Description attribute, some control the behavior of the characteristic, such as the Client Characteristic Configuration attribute which is used to enable notify or indicate operations. The BLE stack and SDK tools generate these as necessary based on the settings configured in the GATT Configurator.
Every attribute has a UUID, which may be either 16 bits (e.g., "180a") or 128 bits (e.g., "e7add780-b042-4876-aae1-112855353cc1"). All 16-bit UUIDs are defined by the Bluetooth SIG and are known as adopted UUIDs. All 128-bit UUIDs are custom and may be used for any purpose without approval from the Bluetooth SIG. Two very common 16-bit UUIDs that you will see are 2901, the Characteristic User Description attribute (defined in the User description field of the GATT Editor), and 2902, the Client Characteristic Configuration attribute (created by our SDK when either "notify" or "indicate" is enabled on a characteristic).
Note that some attribute UUIDs do not technically need to be unique. Their handles are always unique, but the UUIDs occasionally overlap. For example, every Client Characteristic Configuration attribute has a UUID of 0x2902, even though there may be a dozen of them in a single GATT database. You should always give your own custom characteristics fully unique UUIDs but don't be alarmed if you are testing out your prototype with Bluetooth NCP Commander, you perform a Descriptor Discovery operation, and suddenly see multiple instances of the same UUID for certain things. This is normal.
Data Transfer Methods#
read, write, notify, and indicate are the four basic operations for moving data in BLE. The BLE protocol specification allows maximum data payload of 247 bytes for these operations. However, for read operations, the supported size is 249 bytes. BLE is built for low-power consumption and for infrequent short-burst data transmissions. Sending large amounts of data is possible, but usually ends up being less efficient than classic Bluetooth when trying to achieve maximum throughput. The following are a few general guidelines about the types of data transfer you will need to use:
If the client needs to send data to the server, use write.
If the client needs to get data from the server on-demand (i.e., polling), use read.
If the server needs to send data to the client without the client requesting it first, use notify or indicate. (The client must subscribe to these updates before any data will be transferred.)
The server can't pull data from the client directly. If this is necessary, the server must use notify or indicate to send pre-arranged request data to the client, and then the client may follow up with a write operation to send back whatever data that the server needs.
The above four BLE data transfer operations are described here. Commands which you must send are shown separately from the events which the stack generates. Complete API reference can be found for example through Simplicity Studio Launcher on the Documentation tab.
Read operation is requested by the GATT client on a specific attribute exposed by the GATT server. The server then responds with the requested value. In the BLE stack, these API methods are typically involved in read operations:
Reads a remote attribute's value with the given handle. A single sl_bt_evt_gatt_characteristic_value event is generated if the characteristic value fits in one ATT PDU. Otherwise, more than one sl_bt_evt_gatt_characteristic_value events are generated because the firmware will automatically use the "read long" GATT procedure. This is the most common method used by a GATT client to read individual attribute values.
Reads the characteristic value of a service from a remote GATT database by giving the UUID of the characteristic and the handle of the service containing this characteristic. A sl_bt_evt_gatt_characteristic_value event is generated if the characteristic value fits in one ATT PDU. Otherwise, more than one sl_bt_evt_gatt_characteristic_value events are generated because the firmware will automatically use the "read long" GATT procedure.
Can be used to read the values of multiple characteristics from a remote GATT database at once. sl_bt_evt_gatt_characteristic_value events are generated as the values are returned by the remote GATT server.
Generated on the GATT server when a client requests reading data from a characteristic whose "type" value is set to "user". Normally, this operation is handled by the stack automatically and the characteristic value stored in RAM right on the module is sent back to the client. However, with "user" characteristics, this data storage and retrieval is entirely up to you. The stack lets you know when it has been requested and you must get it (or generate it), then send it back (or not) in whatever format is desired. This can be useful for immediate I/O status reads, for example, so that instead of maintaining a value in memory or polling all the time so it is kept "fresh," you can instead just wait for the request and read the I/O status on the spot when it comes in. (ONLY applies if Value type="user" is set in the GATT Editor for the given characteristic)
Used to send back the desired data when a sl_bt_evt_gatt_server_user_read_request is generated by a client's request on a "user" characteristic. This command takes a result code and the actual value, if any, to send back. (ONLY applies if Value type="user" is set in the GATT editor for the given characteristic)
This operation is requested by the GATT client on a specific attribute exposed by the GATT server, and a new value to write is provided at the same time. The server then stores the new value and (optionally) acknowledges the write operation back to the client. In the BLE stack, these API methods are typically involved in write operations:
Writes a remote attribute's value on a GATT server. This performs an acknowledged write operation, so the server will respond when the value has been successfully written. If the given value does not fit in one ATT PDU, "write long" GATT procedure is used automatically. This is the most common method used by a GATT client to write individual attribute values.
This event indicates that the current GATT procedure has been completed successfully or that it has failed with an error. All GATT commands excluding sl_bt_gatt_write_characteristic_value_without_response and sl_bt_gatt_send_characteristic_confirmation will trigger this event, so the application must wait for this event before issuing another GATT command (excluding the two aforementioned exceptions). This event is useful for controlling program flow based on whether a critical operation has finished yet or not.
Writes a remote attribute's value on a GATT server. This is exactly the same as the above command except that it is unacknowledged. There will be no response upon completion. Like the "notify" operation, this means that it is faster (multiple unacknowledged writes may be performed within a single connection interval), but less reliable than acknowledged writes.
Can be used to add a characteristic value to the write queue of a remote GATT server. This command can be used in cases where very long attributes need to be written or a set of values needs to be written atomically. At most 245 (ATT_MTU - 5) amount of data can be sent once. The sl_bt_evt_gatt_procedure_completed event occurs when the preparation is done. In all cases where the amount of data to transfer fits into the BGAPI payload the command sl_bt_gatt_write_characteristic_value is recommended for writing long values since it transparently performs the "prepare_write" and "execute_write" commands.
The following is an example of the flow of queuing writes:
Executes (flags = 1) or cancels (flags = 0) any pending write operations queued with
sl_bt_gatt_prepare_characteristic_value_write. The stack ha