
Bluetooth Low Energy (BLE) is a game-changer for IoT devices, allowing them to run for years on a single coin cell battery.
To develop a BLE GATT server, you need to define a GATT database that includes one or more services. A service is a collection of characteristics that provide a specific functionality.
A GATT server can have multiple services, each with its own characteristics. This allows you to create complex devices with multiple features.
GATT services are organized into a tree-like structure, with a root node and branches that represent the services and their characteristics.
See what others are reading: Why Is Bluetooth Not Finding Devices Android
Bluetooth LE GATT Basics
Bluetooth LE GATT is a protocol for exchanging data between devices, which is used for applications like fitness tracking and smart home devices.
GATT stands for Generic Attribute Profile, and it's a standardized way for devices to communicate with each other.
A GATT server is responsible for managing the services and characteristics that are exposed to a GATT client.
Related reading: What Is Nearby Devices Permission in Android
GATT clients are devices that connect to a GATT server to access its services and characteristics.
GATT services are collections of related characteristics that provide a specific function or set of functions.
Characteristics are the basic building blocks of GATT services, and they can be either read or written by a GATT client.
Bluetooth LE devices can have multiple GATT services, each with its own set of characteristics.
Intriguing read: How to Remove Paired Bluetooth Devices Samsung
GATT Architecture
GATT Architecture is the backbone of Bluetooth Low Energy (BLE) communication. It defines the format of services and their characteristics, and the procedures used to interact with these attributes.
Services and characteristics are types of attributes that serve a specific purpose, and characteristics are the lowest level attribute within a database of attributes. This hierarchy allows for a structured approach to data exposure.
GATT takes on the same roles as the Attribute Protocol (ATT), and these roles are determined per transaction, not per device. This means a device can act as both a server and a client during the same connection.
GATT defines procedures for service discovery, characteristic reads, characteristic writes, notifications, and indications. These procedures enable devices to communicate effectively.
The GATT architecture provides a foundation for BLE communication, allowing devices to interact with each other in a standardized way.
Security and Authentication
GATT servers can define permissions for each characteristic independently, allowing some to be accessed by any client while limiting others to authenticated or authorized clients.
These permissions are usually defined as part of a higher-level profile specification, but for custom profiles, users can select them as they see fit.
Characteristics that require authentication cannot be accessed until the client has gone through an authenticated pairing method.
This verification is performed within the stack, with no processing required by the application, as long as the characteristic is registered properly with the GATT server.
If an un-authenticated client attempts to read an authenticated characteristic, the GATT server will automatically reject it with ERROR_INSUFFICIENT_AUTHEN(0x41).
The GATT server will not invoke the callback function for the characteristic, as seen in the Sniffer Capture Example.
Service Declaration
The Service Declaration is a crucial part of Bluetooth LE GATT, and it's essential to understand how it works.
The Service Declaration attribute is used to declare a GATT service, and it's typically represented by a UUID (Universally Unique Identifier).
The type of the Service Declaration attribute is set to the Bluetooth SIG-defined primary service UUID (0x2800).
The permission for reading the Service Declaration attribute is set to GATT_PERMIT_READ, which means that a GATT client is permitted to read this service.
The pValue of the Service Declaration attribute is a pointer to the UUID of the service, which is custom-defined as 0xFFF0 in our example.
Here's a breakdown of the Service Declaration attribute:
The Service Declaration attribute is used to identify the service and its characteristics, and it's a fundamental part of the GATT service declaration process.
Service Operations
Service Operations can be a bit complex, but essentially, they involve declaring and managing services on a Bluetooth LE device. You can declare a service using a service declaration attribute, such as the simple_gatt_profile service declaration attribute, which specifies the service type, permissions, and other characteristics.
To enable the GATT service changed feature, you need to use a supported build config and enable the feature in the project's build_config.opt file. Once enabled, the GAPBondMgr will handle sending the service changed indication to connected clients.
Here are the steps to enable the GATT service changed feature:
- Use a supported build config for the stack.
- Enable the feature with the project's build_config.opt file by uncommenting the line -DBLE_V41_FEATURES=L2CAP_COC_CFG+V41_CTRL_CFG.
- On the client side, service changed indications can be registered using the same method as registering for other indications.
Perform Read/Write Operations

The characteristic is the fundamental unit of GATT-based communication, containing a value that represents a distinct piece of data on the device. For example, the battery level characteristic has a value that represents the battery level of the device.
To perform read/write operations on a characteristic, you need to read its properties first to determine what operations are supported.
The GATT layer handles most of the GATT functionality, but as a GATT server, you don't need to make direct calls to GATT layer functions. Instead, you interface with the profiles that configure the GATTServApp module.
Read operations are supported when the characteristic has a read property, allowing you to read the value. Writing to a characteristic follows a similar pattern, but you need to use the DataReader and DataWriter APIs to work with raw buffers.
Here's a summary of the steps to perform read/write operations on a characteristic:
- Read the characteristic properties to determine what operations are supported.
- Use the DataReader and DataWriter APIs to work with raw buffers.
- Perform the read/write operation based on the supported operations.
The profile containing the characteristics should provide set and get abstraction functions for the application to read and write a characteristic of the profile. These functions should also include logic to check for and implement notifications and indications if the relevant characteristic has notify or indicate properties.
Midi Service.C

The midi_service.c file is where the implementation of the MIDI service takes place, including initialization, service creation, and event handling.
This file is responsible for handling the connection event by setting and storing the connection handle. The disconnection event is also handled by resetting the connection handle associated with the service.
The write handler function processes the write event and verifies its validity, leaving the implementation and processing to the developer. It also handles notifications being enabled or disabled and passes this event back to the main application.
The BLE event handler function is automatically called by the SoftDevice and is set up by the BLE_MIDI_DEF macro. It processes the BLE event and hands it off to the appropriate function to be processed, handling connection, disconnection, and write events.
The service initializer function, ble_midi_service_init(), is also implemented in this file. It includes the header file for the MIDI service and modifies the advertised Device Name to indicate this is the MIDI example.
Here are the key functions implemented in the midi_service.c file:
- Handle connection event by setting and storing connection handle
- Handle disconnection event by resetting connection handle
- Process write event and verify its validity
- Handle BLE events, including connection, disconnection, and write events
- Implement service initializer function, ble_midi_service_init()
Client-Server Relationships
Client-server relationships in Bluetooth Low Energy (BLE) technology are a crucial aspect of GATT. The smart fitness tracker I use acts as a server, providing information about my workout to the computer, which acts as a client, reading that workout information.
In BLE technology, GAP and GATT roles are independent of each other, allowing peripheral or central devices to be servers or clients depending on the direction of data flow. For example, if I wanted to configure settings or update firmware from the computer to the smart fitness tracker, the computer would act as a server, sending the necessary data or commands.
GATT clients don't have attribute tables or profiles, as they're gathering information, not serving it. Most interfacing with the GATT layer occurs directly from the application, making it a straightforward process to implement client-server relationships in BLE technology.
Broaden your view: Bluetooth Technology in Headphones
Client-Server Relationships
In a client-server relationship, one device acts as a server, providing information to another device acting as a client. This is the case with a smart fitness tracker acting as a server, providing fitness data to a computer running a fitness application.
The direction of data flow determines whether a device is a server or client, and it's independent of the GAP role. For instance, if you want to configure settings or update firmware from the computer to the smart fitness tracker, the computer would act as a server, sending the necessary data or commands.
A client-server relationship is essential in Bluetooth Low Energy (BLE) technology, where devices can be either servers or clients depending on the direction of data flow. This means that a peripheral device can act as a server, providing information to a central device acting as a client.
Here's a breakdown of the roles:
- Server: Provides information to a client
- Client: Receives information from a server
A device can act as both a server and client, depending on the situation. For example, a smart fitness tracker can act as a server, providing fitness data to a computer, but also as a client, receiving firmware updates from the computer.
In a BLE device, the GATT layer is an abstraction of the ATT layer, and it's used for client-server relationships. The GATT client abstraction is used when a device is acting as a GATT client, gathering information from a server.
Client Characteristic Configuration
The client characteristic configuration is a crucial aspect of client-server relationships in Bluetooth Low Energy (BLE) communication. It's represented as an array because this value must be cached for each connection.
The client characteristic configuration type is set to the Bluetooth SIG-defined client characteristic configuration UUID (0x2902), which is a required attribute for GATT clients. This type must be readable and writable.
Here are some key characteristics of client characteristic configuration:
- Type: 0x2902 (Bluetooth SIG-defined client characteristic configuration UUID)
- Properties: Readable and writable
In the simple_gatt_profile, the client characteristic configuration is defined as follows:
- pValue: A pointer to the location of the client characteristic configuration array
- simpleProfileChar4Config: A pointer to the client characteristic configuration array
The GATTServApp_InitCharCfg function is used to initialize the CCC arrays, which are persistent between power downs and between bonded device connections. This function tries to initialize the CCCs with information from a previously bonded connection and set the initial values to default values if not found.

Here is a step-by-step guide to initializing the client characteristic configuration:
1. Allocate space for the client characteristic configuration arrays.
2. Initialize the CCC arrays using the GATTServApp_InitCharCfg function.
3. Register the profile with the GATTServApp, passing the attribute table of the profile and pointers to the callbacks of the profile.
Data Management
Data Management is crucial for Bluetooth LE GATT, and it's all about how data is formatted, packaged, and exchanged between devices.
The GATT layer defines hierarchical relationships between attributes, characteristics, services, and profiles. Attributes are grouped into characteristics and are often consolidated into a service.
Profiles consist of one or more services, and the Bluetooth SIG has developed a variety of standard profiles and services for common use cases. Customization is also common, allowing developers to tailor GATT to their specific needs.
If this caught your attention, see: List of Bluetooth Profiles
Queued Writes
Queued Writes allow a GATT server to send more payload data by queuing up multiple write requests. The default queue size is 5.

With a default MTU of 23 and payload of 18 bytes, up to 90 bytes of payload can be sent. This is a significant increase in data transmission capacity.
To adjust the Prepare Write queue, use GATTServApp_SetParameter() with parameter GATT_PARAM_NUM_PREPARE_WRITES. There is no specified limit, but it is bounded by the available HEAPMGR space.
You can refer to the Bluetooth Core Specification Version 4.2 for more information on Queued Writes, specifically in the section [Vol 3], Part F, Section 3.4.6.
Data Management
Data Management is a crucial aspect of any system, and it's no different in the world of Bluetooth devices. GATT, or Generic Attribute Profile, focuses on how data is formatted, packaged, and exchanged between devices.
The GATT layer defines hierarchical relationships between attributes, characteristics, services, and profiles. This structure allows for efficient organization and management of data.
Attributes are grouped into characteristics and are often consolidated into a service. This makes it easier to manage and access specific data.
The Bluetooth SIG has developed a variety of standard profiles and services for common use cases, though customization is also common. This means that you can use pre-defined profiles for common tasks, or create your own to suit your specific needs.
Worth a look: How Do You Use Bluetooth Speakers
Memory Allocation for Procedures

To support fragmentation, GATT and ATT payload structures must be dynamically allocated for commands sent wirelessly. This is particularly important when sending GATT_Notification, as the buffer must be allocated for the notification or indication payload.
For example, when using GATT_Notification() or GATT_Indication() directly, memory management must be added to prevent memory leaks.
To allocate memory for the notification or indication payload, you can use GATT_bm_alloc(), which takes in the connection handle, ATT handle value, and maximum MTU as parameters. If the allocation succeeds, you can send the notification or indication using GATT_Notification() or GATT_Indication().
Here are the steps to follow:
- Try to allocate memory for the notification or indication payload using GATT_bm_alloc().
- Send notification or indication using GATT_Notification() or GATT_Indication() if the allocation succeeds.
If the return value of the notification or indication is SUCCESS (0x00), the stack will automatically free the memory. However, if the return value is something other than SUCCESS, you must manually free the memory using GATT_bm_free().

Here's an example of how to free the memory in the GATTServApp_SendNotiInd function in the gattservapp_util.c file:
```c
noti.pValue = (uint8*)GATT_bm_alloc(connHandle, ATT_HANDLE_VALUE_NOTI, GATT_MAX_MTU, &len);
if (noti.pValue != NULL) {
status = (*pfnReadAttrCB)(connHandle, pAttr, noti.pValue, ¬i.len, 0, len, GATT_LOCAL_READ);
if (status == SUCCESS) {
noti.handle = pAttr->handle;
if (cccValue & GATT_CLIENT_CFG_NOTIFY) {
status = GATT_Notification(connHandle, ¬i, authenticated);
} else {
status = GATT_Indication(connHandle, (attHandleValueInd_t*)¬i, authenticated, taskId);
}
}
if (status != SUCCESS) {
GATT_bm_free((gattMsg_t*)¬i, ATT_HANDLE_VALUE_NOTI);
}
} else {
status = bleNoResources;
}
```
Device Connection and Communication
Device connection and communication is a crucial aspect of Bluetooth Low Energy (BLE) technology. A device can join a BLE network by adopting roles specified in the Generic Access Profile (GAP), such as Peripheral or Central.
A Peripheral device advertises its presence so Central devices can establish a connection. Central devices, on the other hand, initiate a connection with a Peripheral device by first listening to the advertising packets.
To initiate a connection, you can set GattSession.MaintainConnection to true, or call an uncached service discovery method on BluetoothLEDevice, or perform a read/write operation against the device. This will trigger the OS to re-connect when necessary, and you'll get access to the device otherwise it will return with a DeviceUnreachable error.

Here are the possible ways to initiate a connection:
Once a connection is established, Central devices typically establish the connection parameters between the Peripheral device and itself. The Peripheral device can ask the Central device to change the connection parameters.
Connect Device
Connecting to a device is a crucial step in establishing communication. A device can join a BLE network by adopting roles specified in GAP, such as Peripheral or Central.
To initiate a connection, you need to create a BluetoothLEDevice object, but this alone doesn't necessarily connect to the device. To initiate a connection, set GattSession.MaintainConnection to true, or call an uncached service discovery method on BluetoothLEDevice, or perform a read/write operation against the device.
The system will wait indefinitely for a connection if GattSession.MaintainConnection is set to true. However, if you're performing service discovery or read/write operations, the system will wait a finite but variable time, which can be anywhere from instantaneous to several minutes.
Expand your knowledge: Lg Xboom Bluetooth Audio System
There are two main roles in GAP: Peripheral and Central. A Peripheral device advertises its presence, allowing Central devices to establish a connection. A Central device initiates a connection with a Peripheral device by listening to the advertising packets.
Here's a summary of the roles:
Notification and Subscription
To get notifications from a Bluetooth LE device, you need to subscribe to them. This involves writing to the Client Characteristic Configuration Descriptor (CCCD) to tell the server device that you want to know when a particular characteristic value changes.
There are two things to do before getting notifications: write to the CCCD and handle the Characteristic.ValueChanged event. Writing to the CCCD is the first step.
To write to the CCCD, you need to tell the server device that you want to know when a particular characteristic value changes. This is done by writing to the CCCD.
Here are the two things to take care of before getting notifications:
- Write to Client Characteristic Configuration Descriptor (CCCD)
- Handle the Characteristic.ValueChanged event
The GattCharacteristic's ValueChanged event will get called each time the value gets changed on the remote device, if you've written to the CCCD.
Example and Implementation
Let's take a look at some examples of GATT implementations to get a better understanding of how it works.
The Silicon Labs Bluetooth Low Energy development framework (BGLib) uses an example GATT.xml file to define services and characteristics.
This XML file defines two services: the Generic Access service and the Cable Replacement service. The Generic Access service is mandatory per the spec and includes the following mandatory characteristics.
The Cable Replacement service has one characteristic named data.
If you're working with a standardized device like a MIDI device, you can skip most of the steps and simply implement the GATT structure according to the MIDI spec.
The MIDI BLE Specification defines one service and one characteristic: the MIDI Service with a UUID of 03B80E5A-EDE8-4B33-A751-6CE34EC4C700.
To implement GATT on an nRF52 development kit, you'll need an nRF52840 development kit (or an nRF52832 chipset with modifications).
Here's a step-by-step guide to implementing GATT on an nRF52 development kit:
- Use the ble_app_template example provided in the nRF52 SDK (version 15.0.0 or the latest 17.1.0).
- Open the project in Segger Embedded Studio and add the necessary files to the project.
- Modify the example to add the MIDI Service and Characteristic (according to the spec).
- Compile the code and flash it to the nRF52840 development kit.
- Scan for the device via a Client emulator app such as the Nordic nRF Connect App (iOS, Android, or desktop).
- Connect to the device and discover the Services and Characteristics.
- Verify that the GATT matches the MIDI spec.
Development Considerations
Implementing GATT and GAP in Bluetooth Low Energy (BLE) development requires a deep understanding of their intricate roles and procedures. This can be overwhelming, especially for beginners.
You can find a more comprehensive overview of their implementation in The Ultimate Guide to BLE Connectivity Architecture. This resource will help you navigate the complex landscape of BLE development.
Security is a top priority in GATT, and implementing it without compromising efficiency requires careful consideration. This is where BLE Security: Where to Begin When Creating a Connected System comes in handy.
Connection management is crucial in BLE development, as it affects power, speed, and reliability balance. Effectively managing connection parameters will help you achieve optimal performance.
Data throughput and latency are also essential in GATT transactions, especially in real-time applications. Maximizing throughput while minimizing latency can be achieved by following the guidance in the Ultimate Guide to Managing Your BLE Connection.
Debugging issues in wireless communication can be complex and time-consuming. Our BLE development and debugging tool, LightBlue, is a free resource that can help you identify and resolve issues.
You might enjoy: Pixel 9 Bluetooth Issues

Here are some key considerations for GATT design:
- Mandatory services and characteristics should be implemented.
- Vendor SDKs usually provide APIs for setting the name and appearance, but it's recommended to explicitly implement the mandatory service.
- Bluetooth SIG-adopted profiles, services, and characteristics should be utilized in your design whenever possible.
- Group characteristics that serve related functionality within a single service.
- Avoid having services with too many characteristics.
Frequently Asked Questions
What is Bluetooth LE generic?
Bluetooth LE generic refers to the Generic Attribute Profile (GATT), a framework for organizing Bluetooth Low Energy profiles into standardized services and use cases. GATT enables developers to create consistent and interoperable Bluetooth LE applications.
Featured Images: pexels.com

