Generic Attribute Profile (GATT)¶
Just as the GAP layer handles most connection-related functionality, the GATT layer of the Bluetooth Low Energy protocol stack is used by the application for data communication between two connected devices. Data is passed and stored in the form of characteristics which are stored in memory on the Bluetooth Low Energy device. From a GATT standpoint, when two devices are connected they are each in one of two roles.
The GATT server
the device containing the characteristic database that is being read or written by a GATT client.
The GATT client
the device that is reading or writing data from or to the GATT server.
Figure 72. shows this relationship in a sample Bluetooth low energy connection where the peripheral device (that is, a CC13xx or CC26xx LaunchPad) is the GATT server and the central device (that is, a smart phone) is the GATT client.
The GATT roles of client and server are independent from the GAP roles of peripheral and central. A peripheral can be either a GATT client or a GATT server, and a central can be either a GATT client or a GATT server. A peripheral can act as both a GATT client and a GATT server. For a hands-on review of GATT services and characteristics, see SimpleLink Academy.
GATT Characteristics and Attributes¶
While characteristics and attributes are sometimes used interchangeably when referring to Bluetooth Low Energy, consider characteristics as groups of information called attributes. Attributes are the information actually transferred between devices. Characteristics organize and use attributes as data values, properties, and configuration information. A typical characteristic is composed of the following attributes.
Characteristic Value
data value of the characteristic
Characteristic Declaration
descriptor storing the properties, location, and type of the characteristic value
Client Characteristic Configuration
a configuration that allows the GATT server to configure the characteristic to be notified (send message asynchronously) or indicated (send message asynchronously with acknowledgment)
Characteristic User Description
an ASCII string describing the characteristic
These attributes are stored in the GATT server in an attribute table. In addition to the value, the following properties are associated with each attribute.
Handle
the index of the attribute in the table (Every attribute has a unique handle.)
Type
indicates what the attribute data represents (referred to as a UUID [universal unique identifier]. Some of these are Bluetooth SIG-defined and some are custom.)
Permissions
enforces if and how a GATT client device can access the value of an attribute
GATT Client Abstraction¶
Like the GAP layer, the GATT layer is also abstracted. This abstraction depends on whether the device is acting as a GATT client or a GATT server. As defined by the Bluetooth Core Specifications Version 5.2, the GATT layer is an abstraction of the ATT layer.
GATT clients do not have attribute tables or profiles as they are gathering, not serving, information. Most of the interfacing with the GATT layer occurs directly from the application.
GATT Server Abstraction¶
As a GATT server, most of the GATT functionality is handled by the individual GATT profiles. These profiles use the GATTServApp ( see BLE Stack API Reference, GATTServApp Section) (a configurable module that stores and manages the attribute table). Figure 74. shows this abstraction hierarchy.
The design process involves creating GATT profiles that configure the GATTServApp module and use its API to interface with the GATT layer. In this case of a GATT server, direct calls to GATT layer functions are unnecessary. The application then interfaces with the profiles.
GATT Services and Profile¶
A GATT service is a collection of characteristics. For example, the heart rate service contains a heart rate measurement characteristic and a body location characteristic, among others. Multiple services can be grouped together to form a profile. Many profiles only implement one service so the two terms are sometimes used interchangeably.
Note
TI intends this section as an introduction to the attribute table by using simple_peripheral as an example. For information on how this profile is implemented within the stack, see GATT Server Abstraction.
There are four GATT profiles defined in the simple_peripheral example application project.
-
This service contains device and access information such as the device name, vendor identification, and product identification.
The following characteristics are defined for this service:
Device name
Appearance
Peripheral preferred connection parameters
Note
See the Gap Service and Characteristics for GATT Server section ([Vol. 3], Part C, Section 12) of the Bluetooth Core Specifications Version 5.2 for more information on these characteristics.
Generic Attribute Service
This service contains information about the GATT server, is a part of the Bluetooth Low Energy protocol stack, and is required for every GATT server device as per the Bluetooth Core Specifications Version 5.2.
Device Info Service
This service exposes information about the device such as the hardware, software version, firmware version, regulatory information, compliance information, and manufacturer name. The Device Info Service is part of the Bluetooth Low Energy protocol stack and configured by the application.
simple_gatt_profile Service
This service is a sample profile for testing and for demonstration. The full source code is provided in the simple_gatt_profile.c and simple_gatt_profile.h files.
Figure 75. shows the attribute table in the simple_peripheral project.
The simple_gatt_profile contains the following characteristics:
SIMPLEPROFILE_CHAR1
1-byte value that can be read or written from a GATT client device
SIMPLEPROFILE_CHAR2
1-byte value that can be read from a GATT client device but cannot be written
SIMPLEPROFILE_CHAR3
1-byte value that can be written from a GATT client device but cannot be read
SIMPLEPROFILE_CHAR4
1-byte value that cannot be directly read or written from a GATT client device (This value is notifiable: This value can be configured for notifications to be sent to a GATT client device.)
SIMPLEPROFILE_CHAR5
5-byte value that can be read (but not written) from a GATT client device
The following is a line-by-line description of the simple profile attribute table, referenced by the following handle.
0x001C is the simple_gatt_profile service declaration.
This declaration has a UUID of 0x2800 (Bluetooth-defined
GATT_PRIMARY_SERVICE_UUID
). The value of this declaration is the UUID of the simple_gatt_profile (custom-defined).0x001D is the SimpleProfileChar1 characteristic declaration.
This declaration can be thought of as a pointer to the SimpleProfileChar1 value. The declaration has a UUID of 0x2803 (Bluetooth-defined
GATT_CHARACTER_UUID
). The value of the declaration characteristic, as well as all other characteristic declarations, is a 5-byte value explained here (from MSB to LSB):Byte 0 is the properties of the SimpleProfileChar1 as defined in the Bluetooth Core Specifications Version 5.2 (The following are some of the relevant properties.)
0x02: permits reads of the characteristic value
0x04: permits writes of the characteristic value (without a response)
0x08: permits writes of the characteristic value (with a response)
0x10: permits of notifications of the characteristic value (without acknowledgment)
0x20: permits notifications of the characteristic value (with acknowledgment)
The value of 0x0A means the characteristic is readable (0x02) and writeable (0x08).
Bytes 1-2: the byte-reversed handle where the SimpleProfileChar1’s value is (handle 0x001E)
Bytes 3-4: the UUID of the SimpleProfileChar1 value (custom-defined 0xFFF1)
0x001E is the SimpleProfileChar1 Characteristic Value
This value has a UUID of 0xFFF1 (custom-defined). This value is the actual payload data of the characteristic. As indicated by its characteristic declaration (handle 0x01D), this value is readable and writable.
0x001F is the SimpleProfileChar1 Characteristic User Description
This description has a UUID of 0x2901 (Bluetooth-defined). The value of this description is a user-readable string describing the characteristic.
0x0020 - 0x002C
are attributes that follow the same structure as the simpleProfileChar1 described previously with regard to the remaining four characteristics. The only different attribute, handle 0x0028, is described as follows.
0x0028 is the SimpleProfileChar4 Client Characteristic Configuration. This configuration has a UUID of 0x2902 (Bluetooth-defined). By writing to this attribute, a GATT server can configure the SimpleProfileChar4 for notifications (writing 0x0001) or indications (writing 0x0002). Writing 0x0000 to this attribute disable notifications and indications.
GATT Security¶
As described in GATT Server Abstraction, the GATT server may define permissions independently for each characteristic. The server may allow some characteristics to be accessed by any client, while limiting access to other characteristics to only authenticated or authorized clients. These permissions are usually defined as part of a higher level profile specification. For custom profiles, the user may select the permissions as they see fit. For more information about the GATT Security, refer to the Security Considerations section ([Vol 3], Part G, Section 8) of the Bluetooth Core Specifications Version 5.2.
Authentication¶
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. The only requirement is for the characteristic to be registered properly with the GATT server.
For example, characteristic 5 of the simple_gatt_profile allows on authenticated reads.
// Characteristic Value 5
{
{ ATT_BT_UUID_SIZE, simpleProfilechar5UUID },
GATT_PERMIT_AUTHEN_READ,
0,
simpleProfileChar5
},
When an un-authenticated client attempts to read this value, the GATT
server automatically rejects it with ERROR_INSUFFICIENT_AUTHEN (0x41)
,
without invoking the simpleProfile_ReadAttrCB(). See an example of this
in Sniffer Capture Example.
After the client has successfully authenticated, read/write requests are forwarded to the profiles read/write callback. See the code below for a simple_gatt_profile example:
case SIMPLEPROFILE_CHAR5_UUID:
*pLen = SIMPLEPROFILE_CHAR5_LEN;
VOID memcpy( pValue, pAttr->pValue, SIMPLEPROFILE_CHAR5_LEN );
break;
Authorization¶
Authorization is a layer of security provided in addition to what BLE already implements. Because applications are required to define their own requirements for authorization, the stack forwards read/write requests on these characteristics to the application layer of the profile.
For the profile to register for authorization information from the GATT server, it must define an authorization callback with the stack. The simple_gatt_profile does not do this by default, but below is an example of how it could be modified to do this.
Register profile level authorization callback.
CONST gattServiceCBs_t simpleProfileCBs =
{
simpleProfile_ReadAttrCB, // Read callback function pointer
simpleProfile_WriteAttrCB, // Write callback function pointer
simpleProfile_authorizationCB // Authorization callback function pointer
};
Implement authorization callback code.
static bStatus_t simpleProfile_authorizationCB( uint16 connHandle, gattAttribute_t *pAttr, uint8 opcode )
{
//This is just an example implementation, normal use cases would require
//more complex logic to determine that the device is authorized
if(clientIsAuthorized)
return SUCCESS;
else
return ATT_ERR_INSUFFICIENT_AUTHOR;
}
The authorization callback executes in the stack context, thus
intensive processing should not be performed in this function. The
implementation is left up to the developer; the above callback
should be treated as a shell. The return value should be either
SUCCESS
if the client is authorized to access the characteristic, or
ATT_ERR_INSUFFICIENT_AUTHOR
if they have not yet obtained proper
authorization. Authorization requires the connection to be authenticated
beforehand, or ATT_ERR_INSUFFICIENT_AUTHEN
will be sent as an error
response.
If a characteristic that requires authorization is registered with
the GATT server, but no application level authorization callback is
defined, the stack will return ATT_ERR_UNLIKELY
. Because this
error can be cryptic, TI recommends using an authorization callback.
Using the GATT Layer Directly¶
This section describes how to use the GATT layer in an application. The functionality of the GATT layer is implemented in the library but header functions can be found in the gatt.h file. The BLE Stack API Reference within the ATT/GATT section has the complete API for the GATT layer. The Bluetooth Core Specifications Version 5.2 provides more information on the functionality of these commands. These functions are used primarily for GATT client applications. A few server-specific functions are described in the API. Most of the GATT functions returns ATT events to the application, review BLE Stack API Reference for details.
The general procedure to use the GATT layer when functioning as a GATT client (in the simple_central project) is as follows:
Note
Even though the event sent to the application is an ATT
event, it is sent as a GATT protocol stack message
(GATT_MSG_EVENT
).
Note
In addition to receiving responses to its own commands, a GATT client may
also receive asynchronous data from the GATT server as indications or
notifications. Use (GATT_RegisterForInd(selfEntity)
) to receive these
ATT notifications and indications. These notifications and indications are
also sent as ATT events (ATT_HANDLE_VALUE_NOTI
&
ATT_HANDLE_VALUE_IND
) in GATT messages to the application. These events
must be handled as described in GATT Services and Profile.
Maximum Transmission Unit (MTU)¶
The size of the L2CAP PDU also defines the size of the Attribute
Protocol Maximum Transmission Unit (ATT_MTU
). See L2CAP MTU for more
information about L2CAP MTU settings and how it interacts with GATT/ATT.
By default, LE devices assume the size of the L2CAP PDU is 27 bytes, which
corresponds to the maximum size of the LE packet that can transmit
in a single connection event packet. The L2CAP protocol header for a B-frame
is 4 bytes, resulting in a default size for ATT_MTU
of 23. See
L2CAP Frames and Overhead for more information.
Note
When using the LE Data Length Extension feature, the length of the LE packet can be up to 251 bytes. See LE Data Length Extension (DLE)
Configuring for Larger MTU Values¶
A client device can request a larger ATT_MTU
during a connection by
using the GATT_ExchangeMTU() command.
During this procedure, the client informs the server of its maximum
supported receive MTU size and the server responds with its maximum supported
receive MTU size. Only the client can initiate this procedure. When
the messages are exchanged, the ATT_MTU
for the duration of the
connection is the minimum of the client MTU and server MTU values.
If the client indicates it can support an MTU of 200 bytes and the
server responds with a maximum size of 150 bytes, the ATT_MTU
size
is 150 for that connection.
For more information, see the MTU Exchange section of the Bluetooth Core Specifications Version 5.2.
Take the following steps to configure the stack to support larger MTU values.
Set the
MAX_PDU_SIZE
preprocessor symbol in the application project to the desired value to the maximum desired size of the L2CAP PDU size. The maximum is set by the following equationATT_MTU=MAX_PDU_SIZE-L2CAP_HDR_SIZE
Call GATT_ExchangeMTU() after a connection is formed (GATT client only). The MTU parameter passed into this function must be less than or equal to the definition from step 1.
Receive the
ATT_MTU_UPDATED_EVENT
in the calling task to verify that the MTU was successfully updated. This update requires the calling task to have registered for GATT messages. See Registering to Receive Additional GATT Events in the Application for more information.
Though the stack can be configured to support a MAX_PDU_SIZE
up to
255 bytes, each Bluetooth Low Energy connection initially uses the
default 27 bytes (ATT_MTU
= 23 bytes) value until the exchange MTU
procedure results in a larger MTU size. The exchange MTU procedure
must be performed on each Bluetooth Low Energy connection and must
be initiated by the client.
Note
If the Secure Connections BLE 4.2 Feature is enabled,
the local device will support an MTU of 65 by default. This is required
by LE Secure Connections. Remember that that the actual resulting MTU
must still be negotiated via MTU exchange procedure using
GATT_ExchangeMTU
. All connections will begin with a default MTU of 23
bytes.
See ble_user_config.h
for details.
Increasing the size of the ATT_MTU
increases the amount of data
that can be sent in a single ATT packet. The longest attribute that
can be sent in a single packet is (ATT_MTU
-1) bytes. Procedures,
such as notifications, have additional length restrictions. If an
attribute value has a length of 100 bytes, a read of this entire
attribute requires a read request to obtain the first (ATT_MTU
-1)
bytes, followed by multiple read blob request to obtain the
subsequent (ATT_MTU
-1) bytes. To transfer the entire 100 bytes of
payload data with the default ATT_MTU
= 23 bytes, five request or
response procedures are required, each returning 22 bytes. If the
exchange MTU procedure was performed and an ATT_MTU
was configured
to 101 bytes (or greater), the entire 100 bytes could be read in a
single read request or response procedure.
Note
Due to memory and processing limitations, not all
Bluetooth Low Energy systems support larger MTU sizes. Know the
capabilities of expected peer devices when defining the behavior of
the system. If the capability of peer devices is unknown, design the
system to work with the default 27-byte L2CAP PDU/23-byte ATT_MTU
size. For example, sending notifications with a length greater than
20 bytes (ATT_MTU
-3) bytes results in truncation of data on devices
that do not support larger MTU sizes.
GAP GATT Service (GGS)¶
The GAP GATT Service (GGS) is required for Bluetooth low-energy devices that implement the central or peripheral GAP role. Multirole devices that implement either of these roles must also contain the GGS. The purpose of the GGS is to aid in the device discovery and the connection initiation process.
Note
For more information about the GGS, refer to the GAP Service and and Characteristics for GATT Server section ([Vol 3], Part C, Section 12) of the Bluetooth Core Specifications Version 5.2.
Using the GGS¶
The GAP GATT Service is implemented as part of the Bluetooth Low Energy library
code and the related APIs are found in GATTServApp
and `ATT_GATT
sections in BLE Stack API Reference.
This section describes what the application must do to configure, start, and use the GAP Gatt Service.
Include the header file.
#include "gapgattserver.h"
Initialize the GGS parameters. Code snippet example from simple peripheral.
1 // GAP GATT Attributes
2 static uint8_t attDeviceName[GAP_DEVICE_NAME_LEN] = "Simple BLE Peripheral";
3
4 GGS_SetParameter(GGS_DEVICE_NAME_ATT, GAP_DEVICE_NAME_LEN, attDeviceName);
Initialize the application callbacks with GGS (optional). This notifies the application when any of the characteristics in the GGS have changed.
GGS_RegisterAppCBs(&appGGSCBs);
Add the GGS to the GATT server.
bStatus_t GGS_AddService(GATT_ALL_SERVICES);
Generic Attribute Profile Service (GATT Service)¶
The Generic Attribute Profile (GATT) Service provides information about the GATT services registered with a device.
Note
For more information on the GATT Service, refer to the Defined Generic Attribute Profile Service section ([Vol 3], Part G, Section 7) of the Bluetooth Core Specifications Version 5.2.
The service changed characteristic is used to inform bonded devices that services have changed on the server upon reconnection. Service changed updates are sent in the form of GATT indications, and the service changed characteristic is not writeable or readable. In the TI Bluetooth Low Energy stack, the service changed characteristic is implemented as part of the gattservapp, which is part of library code.
Per the Bluetooth Core Specifications Version 5.2, it is safe for server devices whose characteristic tables do not change over their lifetime to exclude the service changed characteristic. Support for indications from this characteristic must be supported on GATT client devices.
Using the GATT Service¶
This section describes what the user must do to enable the GATT service changed feature in the Bluetooth Low Energy stack. Once the service changed feature is enabled, the GAPBondMgr will handle sending the service changed indication to clients who have enabled it using the CCCD.
Use a supported build config for the stack; only stack libraries with v4.1 features and L2CAP connection-oriented channels will support the service changed characteristic.
Enable this feature with the project’s
build_config.opt
file, by uncommenting the following line:-DBLE_V41_FEATURES=L2CAP_COC_CFG
Alternatively, if you’re using a single-project example enable the option “L2CAP Connection Oriented Channels” in SysConfig. The “L2CAP Connection Oriented Channels” option has to be enabled in the “BLE” menu of SysConfig. (If SysConfig is not available then add the line
-DBLE_V41_FEATURES=L2CAP_COC_CFG
to thebuild_config.opt
file.)
From this point, the GAPBondMgr handles sending an indication to connected clients when the service has changed and the CCCD is enabled. If the feature is enabled, the peripheral role invokes the necessary APIs to send the indication through the GAPBondMgr.
On the client side, service changed indications can be registered using the same method as registering for other indications.
GATTServApp Module¶
The GATT Server Application (GATTServApp) stores and manages the application-wide attribute table. Various profiles use this module to add their characteristics to the attribute table. The Bluetooth Low Energy stack uses this module to respond to discovery requests from a GATT client. For example, a GATT client may send a Discover all Primary Characteristics message. The Bluetooth Low Energy stack on the GATT server side receives this message and uses the GATTServApp to find and send over-the-air all of the primary characteristics stored in the attribute table. This type of functionality is beyond the scope of this document and is implemented in the library code. The GATTServApp functions accessible from the profiles are defined in ‘’gattservapp_util.c’’ and the API described in BLE Stack API Reference (GATTServApp section). These functions include finding specific attributes and reading or modifying client characteristic configurations. See Figure 78. for an example of how GATTServApp functions in an application.
Building up the Attribute Table¶
Upon power-on or reset, the application builds the GATT table by using the GATTServApp to add services. Each service consists of a list of attributes with UUIDs, values, permissions, and read and write call-backs. As Figure 78. shows, all of this information is passed through the GATTServApp to GATT and stored in the stack.
Attribute table initialization must occur in the application initialization function, that is, simple_peripheral_init().
// Initialize GATT attributes
GGS_AddService(GATT_ALL_SERVICES); // GAP
GATTServApp_AddService(GATT_ALL_SERVICES); // GATT attributes
Implementing Profiles in Attributes Table¶
This section describes the general architecture for implementing profiles and provides specific functional examples in relation to the simple_gatt_profile in the simple_peripheral project. See GATT Services and Profile for an overview of the simple_gatt_profile.
Attribute Table Definition¶
Each service or group of GATT attributes must define a fixed size attribute table that gets passed into GATT. This table in simple_gatt_profile.c is defined as the following.
static gattAttribute_t simpleProfileAttrTbl[SERVAPP_NUM_ATTR_SUPPORTED]
Each attribute in this table is of the following type.
typedef struct attAttribute_t
{
gattAttrType_t type; //!< Attribute type (2 or 16 octet UUIDs)
uint8 permissions; //!< Attribute permissions
uint16 handle; //!< Attribute handle - assigned internally by attribute server
uint8* const pValue; //!< Attribute value - encoding of the octet array is defined in
//!< the applicable profile. The maximum length of an attribute
//!< value shall be 512 octets.
} gattAttribute_t;
When utilizing gattAttribute_t, the various fields have specific meanings.
gattAttrType_t type
type is the UUID associated with the attribute being placed into the table. gattAttrType_t itself is defined as:
typedef struct { uint8 len; //!< Length of UUID (2 or 16) const uint8 *uuid; //!<Pointer to UUID } gattAttrType_t;
Where length can be either
ATT_BT_UUID_SIZE
(2 bytes), orATT_UUID_SIZE
(16 bytes). The *uuid is a pointer to a number either reserved by Bluetooth SIG (defined in gatt_uuid.c) or a custom UUID defined in the profile.uint8 permissions
enforces how and if a GATT client device can access the value of the attribute. Possible permissions are defined in gatt.h as follows:
#define GATT_PERMIT_READ
0x01
//!< Attribute is Readable
#define GATT_PERMIT_WRITE
0x02
//!< Attribute is Writable
#define GATT_PERMIT_AUTHEN_READ
0x04
//!< Read requires Authentication
#define GATT_PERMIT_AUTHEN_WRITE
0x08
//!< Write requires Authentication
#define GATT_PERMIT_AUTHOR_READ
0x10
//!< Read requires Authorization
#define GATT_PERMIT_AUTHOR_WRITE
0x20
//!< Write requires Authorization
#define GATT_PERMIT_ENCRYPT_READ
0x40
//!< Read requires Encryption
#define GATT_PERMIT_ENCRYPT_WRITE
0x80
//!< Write requires Encryption
Allocating Memory for GATT Procedures further describes authentication, authorization, and encryption.
uint16 handle
handle is a placeholder in the table where GATTServApp assigns a handle. This placeholder is not customizable. Handles are assigned sequentially.
uint8* const pValue
pValue is a pointer to the attribute value. The size is unable to change after initialization. The maximum size is 512 octets.
Service Declaration¶
Consider the following simple_gatt_profile service declaration attribute:
// Simple Profile Service
{
{ ATT_BT_UUID_SIZE, primaryServiceUUID }, // type
GATT_PERMIT_READ, // permissions
0, // handle
(uint8 *)&simpleProfileService // pValue
}
The type is set to the Bluetooth SIG-defined primary service UUID
(0x2800). A GATT client is permitted to read this service with the permission
is set to GATT_PERMIT_READ
. The pValue is a pointer to the UUID of
the service, custom-defined as 0xFFF0. SimpleProfileService itself is defined to
reference the profile’s UUID.
// Simple Profile Service attribute
static CONST gattAttrType_t simpleProfileService =
{
ATT_BT_UUID_SIZE,
simpleProfileServUUID
};
Characteristic Declaration¶
Consider the following simple_gatt_profile simpleProfileCharacteristic1 declaration.
// Characteristic 1 Declaration
{
{ ATT_BT_UUID_SIZE, characterUUID },
GATT_PERMIT_READ,
0,
&simpleProfileChar1Props
},
The type is set to the Bluetooth SIG-defined characteristic UUID
(0x2803). This makes simpleProfileCharacteristic1 read only to the GATT client
with the permissions set to GATT_PERMIT_READ
.
For functional purposes, the only information required to be passed to the GATTServApp in pValue is a pointer to the properties of the characteristic value. The GATTServApp adds the UUID and the handle of the value. These properties are defined to allow this particular characteristic in this service to be read and written to.
// Simple Profile Characteristic 1 Properties
static uint8 simpleProfileChar1Props = GATT_PROP_READ | GATT_PROP_WRITE;
Note
An important distinction exists between these properties and the GATT permissions of the characteristic value. These properties are visible to the GATT client stating the properties of the characteristic value. The GATT permissions of the characteristic value affect its functionality in the protocol stack. These properties must match that of the GATT permissions of the characteristic value.
Characteristic Value¶
Consider the simple_gatt_profile simpleProfileCharacteristic1 value.
// Characteristic Value 1
{
{ ATT_BT_UUID_SIZE, simpleProfilechar1UUID },
GATT_PERMIT_READ | GATT_PERMIT_WRITE,
0,
&simpleProfileChar1
},
The type is set to the custom-defined simpleProfilechar1 UUID (0xFFF1). The properties of the characteristic value in the attribute table must match the properties from the characteristic value declaration. The pValue is a pointer to the location of the actual value, statically defined in the profile as follows.
// Characteristic 1 Value
static uint8 simpleProfileChar1 = 0;
Client Characteristic Configuration¶
Consider the simple_gatt_profile simpleProfileCharacteristic4 configuration.
// Characteristic 4 configuration
{
{ ATT_BT_UUID_SIZE, clientCharCfgUuID },
GATT_PERMIT_READ | GATT_PERMIT_WRITE,
0,
(uint8 *)&simpleProfileChar4Config
},
The type is set to the Bluetooth SIG-defined client characteristic configuration UUID (0x2902) GATT clients must read and write to this attribute so the GATT permissions are set to readable and writable. The pValue is a pointer to the location of the client characteristic configuration array, defined in the profile as follows.
1static gattCharCfg_t *simpleProfileChar4Config;
Note
Client characteristic configuration is represented as an array because this value must be cached for each connection. The catching of the client characteristic configuration is described in more detail in Add Service Function.
Add Service Function¶
As described in GATTServApp Module, when an application starts up it requires adding the GATT services it supports. Each profile needs a global AddService function that can be called from the application. Some of these services are defined in the protocol stack, such as GAP GATT Service and GATT Service. User-defined services must expose their own AddService function that the application can call for profile initialization. Using SimpleProfile_AddService() as an example, these functions should do as follows.
Allocate space for the client characteristic configuration (CCC) arrays. As an example, a pointer to one of these arrays was initialized in the profile as described in ref:client_characteristic_configuration.
In the AddService function, the supported connections is declared and memory is allocated for each array. Only one CCC is defined in the imple_gatt_profile but there can be multiple CCCs.
// Allocate Client Characteristic Configuration table
simpleProfileChar4Config = (gattCharCfg_t *)ICall_malloc(sizeof(gattCharCfg_t) * linkDBNumConns );
if ( simpleProfileChar4Config == NULL )
{
return ( bleMemAllocError );
}
Initialize the CCC arrays. CCC values are persistent between power downs and between bonded device connections. For each CCC in the profile, the GATTServApp_InitCharCfg() function must be called. 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.
1GATTServApp_InitCharCfg( INVALID_CONHANDLE, simpleProfileChar4Config );
Register the profile with the GATTServApp. This function passes the attribute table of the profile to the GATTServApp so that the attributes of the profile are added to the application-wide attribute table managed by the protocol stack and handles are assigned for each attribute. This also passes pointers to the callbacks of the profile to the stack to initiate communication between the GATTServApp and the profile.
// Register GATT attribute list and CBs with GATT Server App
status = GATTServApp_RegisterService( simpleProfileAttrTbl,
GATT_NUM_ATTRS( simpleProfileAttrTbl ),
GATT_MAX_ENCRYPT_KEY_SIZE,
&simpleProfileCBs );
Register Application Callback Function¶
Profiles can relay messages to the application using callbacks. In the simple_peripheral project, the simple_gatt_profile calls an application callback whenever the GATT client writes a characteristic value. For these application callbacks to be used, the profile must define a Register Application Callback function that the application uses to set up callbacks during its initialization. The register application callback function for the simple_gatt_profile is the following:
1bStatus_t SimpleProfile_RegisterAppCBs( simpleProfileCBs_t *appCallbacks )
2{
3 if ( appCallbacks )
4 {
5 simpleProfile_AppCBs = appCallbacks;
6 return ( SUCCESS );
7 }
8
9 else
10 {
11 return ( bleAlreadyInRequestedMode );
12 }
13}
Where the callback typedef is defined as the following.
typedef struct
{
simpleProfileChange_t pfnSimpleProfileChange; // Called when characteristic value changes
} simpleProfileCBs_t;
The application must then define a callback of this type and pass it to the simple_gatt_profile with the SimpleProfile_RegisterAppCBs() function. This occurs in simple_peripheral.c as follows.
//Simple GATT Profile Callbacks
#ifndef FEATURE_OAD_ONCHIP
static simpleProfileCBs_t SimpleBLEPeripheral_simpleProfileCBs =
{
SimpleBLEPeripheral_charValueChangeCB // Characteristic value change callback
};
#endif //!FEATURE_OAD_ONCHIP
//...
//Register callback with SimpleGATTprofile
SimpleProfile_RegisterAppCBs(&SimpleBLEPeripheral_simpleProfileCBs);
See Read and Write Callback Functions for further information on how this callback is used.
Read and Write Callback Functions¶
The profile must define Read and Write callback functions which the protocol stack calls when one of the attributes of the profile are written to or read from. The callbacks must be registered with GATTServApp as mentioned in Add Service Function. These callbacks perform the characteristic read or write and other processing (possibly calling an application callback) as defined by the specific profile.
Read Request from Client¶
When a read request from a GATT Client is received for a given attribute, the protocol stack checks the permissions of the attribute and, if the attribute is readable, calls the read call-back profile. The profile copies in the value, performs any profile-specific processing, and notifies the application if desired. This procedure is illustrated in Figure 79. for a read of simpleprofileChar1 in the simple_gatt_profile.
The processing is in the context of the protocol stack. If any intensive profile-related processing that must be done in the case of an attribute read, this should be split up and done in the context of the Application task. See the Write Request from Client for more information.
Note
If desired, the GATT server may choose to delay the handling of the ATT_ReadReq and return a blePending response. A user-defined service can call GATTServApp_ReadRsp() to send the response to the GATT server as soon as the data becomes available. See the Delaying an ATT Read Request for more information.
Write Request from Client¶
When a write request from a GATT client is received for a given attribute, the protocol stack checks the permissions of the attribute and, if the attribute is write-permitted, calls the write callback of the profile. The profile stores the value to be written, performs any profile-specific processing, and notifies the application if desired. Figure 80. shows this procedure for a write of simpleprofileChar3 in the simple_gatt_profile.
Important
Minimizing the processing in protocol stack context is important. In this example, additional processing beyond storing the attribute write value in the profile occurs in the application context by enqueuing a message in the queue of the application.
Get and Set Functions¶
The profile containing the characteristics shall provide set and get abstraction functions for the application to read and write a characteristic of the profile. The set parameter function also includes logic to check for and implement notifications and indications if the relevant characteristic has notify or indicate properties. Figure 81. and the following code show this example for setting simpleProfileChacteristic4 in the simple_gatt_profile.
For example, the application initializes simpleProfileCharacteristic4 to 0 in simple_peripheral.c through the following.
uint8_t charValue4 = 0;
SimpleProfile_SetParameter(SIMPLEPROFILE_CHAR4, sizeof(uint8_t), &charValue4);
The code for this function is displayed in the following code snippet (from simple_gatt_profile.c). Besides setting the value of the static simpleProfileChar4, this function also calls GATTServApp_ProcessCharCfg() because it has notify properties. This action forces GATTServApp to check if notifications have been enabled by the GATT Client. If so, the GATTServApp sends a notification of this attribute to the GATT Client.
1bStatus_t SimpleProfile_SetParameter( uint8 param, uint8 len, void *value )
2{
3bStatus_t ret = SUCCESS switch ( param )
4{
5case SIMPLEPROFILE_CHAR4:
6 if ( len == sizeof ( uint8 ) )
7 {
8 simpleProfileChar4 = *((uint8*)value);
9
10 // See if Notification has been enabled
11 GATTServApp_ProcessCharCfg( simpleProfileChar4Config, &simpleProfileChar4, FALSE,
12 simpleProfileAttrTbl, GATT_NUM_ATTRS( simpleProfileAttrTbl ),
13 INVALID_TASK_ID, simpleProfile_ReadAttrCB );
14 }
Queued Writes¶
Prepare Write commands allows 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. For more information on queued writes, refer to the Queued Writes section ([Vol 3], Part F, Section 3.4.6) of the Bluetooth Core Specifications Version 5.2.
Adjust the Prepare Write queue with GATTServApp_SetParameter() with
parameter GATT_PARAM_NUM_PREPARE_WRITES
. There is no specified
limit, but it is bounded by the available HEAPMGR space.
Allocating Memory for GATT Procedures¶
To support fragmentation, GATT and ATT payload structures must be dynamically allocated for commands sent wirelessly. For example, a buffer must be allocated when sending a GATT_Notification. The stack does this allocation if the preferred method to send a GATT notification or indication is used: calling a SetParameter function of the profile (that is, SimpleProfile_SetParameter()) and calling GATTServApp_ProcessCharCfg() as described in Get and Set Functions.
If using GATT_Notification() or GATT_Indication() directly, memory management must be added as follows.
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.
Note
If the return value of the notification or indication is
SUCCESS (0x00)
, the stack freed the memory.
Use GATT_bm_free() to free the memory if the return value is something other than
SUCCESS
(for example,blePending
).The following is an example of this in the
GATTServApp_SendNotiInd
function in the gattservapp_util.c file.
1noti.pValue = (uint8 *)GATT_bm_alloc( connHandle, ATT_HANDLE_VALUE_NOTI, GATT_MAX_MTU, &len );
2
3if ( noti.pValue != NULL )
4{
5 status = (*pfnReadAttrCB)( connHandle, pAttr, noti.pValue, ¬i.len, 0, len, GATT_LOCAL_READ );
6
7 if ( status == SUCCESS )
8 {
9 noti.handle = pAttr->handle;
10
11 if ( cccValue & GATT_CLIENT_CFG_NOTIFY )
12 {
13 status = GATT_Notification( connHandle, ¬i, authenticated );
14 }
15
16 else // GATT_CLIENT_CFG_INDICATE
17 {
18 status = GATT_Indication( connHandle, (attHandleValueInd_t *)¬i, authenticated, taskId );
19 }
20 }
21
22 if ( status != SUCCESS )
23 {
24 GATT_bm_free( (gattMsg_t *)¬i, ATT_HANDLE_VALUE_NOTI );
25 }
26}
27
28else
29{
30 status = bleNoResources;
31}
For other GATT procedures, take similar steps as noted in BLE Stack API Reference (specifically ATT/GATT section).
Registering to Receive Additional GATT Events in the Application¶
Using GATT_RegisterForMsgs(), receiving additional GATT messages to handle certain corner cases is possible. This possibility can be seen in simple_peripheral_processGATTMsg(). The following three cases are currently handled.
GATT server in the stack was unable to send an ATT response (due to lack of available HCI buffers): Attempt to transmit on the next connection interval. Additionally, a status of
bleTimeout
is sent if the ATT transaction is not completed within 30 seconds, as specified in the Bluetooth Core Specifications Version 5.2.
1// See if GATT server was unable to transmit an ATT response
2if (pMsg->hdr.status == blePending)
3{
4//No HCI buffer was available. Let's try to retransmit the response
5//on the next connection event.
6
7if (HCI_EXT_ConnEventNoticeCmd(pMsg->connHandle, selfEntity, SBP_CONN_EVT_END_EVT) == SUCCESS)
8{
9//First free any pending response
10 SimpleBLEPeripheral_freeAttRsp(FAILURE);
11
12 //Hold on to the response message for retransmission
13 pAttRsp = pMsg;
14
15 //Don't free the response message yet
16 return (FALSE);
17 }
18 }
An ATT flow control violation: The application is notified that the connected device has violated the ATT flow control specification, such as sending a Read Request before an Indication Confirm is sent.
No more ATT requests or indications can be sent wirelessly during the connection. The application may want to terminate the connection due to this violation. As an example in simple_peripheral, the LCD is updated.
1else if (pMsg->method == ATT_FLOW_CTRL_VIOLATED_EVENT)
2{
3 //ATT request-response or indication-confirmation flow control is
4 //violated. All subsequent ATT requests or indications will be dropped.
5 //The app is informed in case it wants to drop the connection.
6
7 //Display the opcode of the message that caused the violation.
8 DISPLAY_WRITE_STRING_VALUE("FC Violated: %d", pMsg->msg.flowCtrlEvt.opcode, LCD_PAGE5);
9}
An ATT MTU size is updated: The application is notified in case this affects its processing in any way. See Maximum Transmission Unit (MTU) for more information on the MTU. As an example in simple_peripheral, the LCD is updated.
1else if (pMsg->method == ATT_MTU_UPDATED_EVENT)
2{
3 // MTU size updated
4 DISPLAY_WRITE_STRING_VALUE("MTU Size: $d", pMsg->msg.mtuEvt.MTU, LCD_PAGE5);
5}
Delaying an ATT Read Request¶
The stack supports a delayed response to the following read requests using a few APIs.
Type of read request
API to be used
ATT_READ_REQ
ATT_READ_BLOB_REQ
ATT_READ_BY_TYPE_REQ
Using GATTServApp_ReadRsp(), delaying an ATT_READ_REQ
from a
registered service is possible. If enabled, the GATT Server can handle read
requests in two ways. When a read request is received, the GATT Server will
issue a callback to the registered service. The callback can return:
SUCCESS
the GATT Server will issue the appropriate ATT_Read_Rsp on behalf of the application
BLE_PENDING
the service can call GATTServApp_ReadRsp() to handle the read request when data is available
To enable this feature, choose the option that applies to your case:
in SysConfig, in the “BLE” menu, activate the option “Delaying an ATT Read Request”
[If SysConfig is not available] define the symbol
ATT_DELAYED_REQ
. For a two-project example,ATT_DELAYED_REQ
has to be defined in the STACK library project.
The service callback function that requires a delay can add the following to its read callback function:
if( (method == ATT_READ_REQ) || (method == ATT_READ_BLOB_REQ) || (method == ATT_READ_BY_TYPE_REQ) )
{
status = blePending;
}
At a later point in the code execution when the data is ready to be sent back, a decision tree similar to the one below can be used:
switch( cmdID )
{
case ATT_READ_REQ:
{
attReadRsp_t rsp;
rsp.pValue = (uint8_t *) GATT_bm_alloc( connId, ATT_READ_RSP, (uint16_t) pCmd->len, NULL );
if( rsp.pValue )
{
rsp.len = (uint16_t) pCmd->len;
memcpy( rsp.pValue, pCmd->val, rsp.len );
Status = ATT_ReadRsp( usConnId , &rsp );
if( Status != SUCCESS )
{
GATT_bm_free( (gattMsg_t * ) &rsp, ATT_READ_RSP );
}
}
else
{
return ( bleMemAllocError );
}
break;
}
case ATT_READ_BLOB_REQ:
{
attReadBlobRsp_t rsp;
rsp.pValue = (uint8_t *) GATT_bm_alloc( connId, ATT_READ_BLOB_RSP, (uint16_t) pCmd->len, NULL );
if( rsp.pValue )
{
rsp.len = (uint16_t) pCmd->len;
memcpy( rsp.pValue, pCmd->val, rsp.len );
xStackStatus = ATT_ReadBlobRsp( usConnId , &rsp );
if( xStackStatus != SUCCESS )
{
GATT_bm_free( (gattMsg_t * ) &rsp, ATT_READ_BLOB_RSP );
}
}
else
{
return ( bleMemAllocError );
}
break;
}
case ATT_READ_BY_TYPE_REQ:
{
Status = GATTServApp_ReadRsp( ConnHandle, pAttrValue, attrLen, attrHandle );
break;
}
}
This procedure is illustrated in Figure 82.
Note
To utilize the GATT Builder tool, please reference Custom GATT Builder for more information on how to build your own services and characteristics.