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.

../_images/gatt_client_server.png

Figure 72. GATT Client and Server Interaction Overview

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.

../_images/gatt_client_abstract.jpg

Figure 73. Visualization of GATT Client Abstraction.

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.

../_images/gatt_server_abstract.jpg

Figure 74. Visualization of GATT Server abstraction.

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.

  • GAP GATT Service (GGS)

    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.

../_images/sbp_attr_table.jpg

Figure 75. Simple GATT Profile Characteristic Table taken with BTool. Red indicates a Profile declaration, Yellow indicates character declaration, and White indicates Attributes related to a particular characteristic declaration.

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.

../_images/image134.jpeg

Figure 76. 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.

  1. 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
};
  1. 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:

@startuml
participant "Application \n(simple_central.c)" as App
participant ICALL
participant "BLE Stack"


App->ICALL: VOID GATT_InitClient(); \nInitialize Client
App->ICALL: GATT_RegisterForInd(selfEntity); \nRegister to receive incoming \nATT Indications or Notification

App->ICALL: GATT_WriteCharValue(connHandle,\n&req, selfEntity);

activate ICALL

  rnote over "ICALL"
  Translate into ATT function
  and send it to the BLE stack
  end note

ICALL->"BLE Stack" : ATT_WriteReq
Deactivate "ICALL"

group Invalid Write Req
  "BLE Stack"-> ICALL : return(INVALIDPARAMETER)
  ICALL-> App : return(INVALIDPARAMETER)
end

group Valid Write Req
  "BLE Stack"-->]: Send ATT_WRITE_REQ

  "BLE Stack"-->ICALL : return(SUCCESS)
  ICALL-->App : return(SUCCESS)
  ...
  ... Wait for the server to respond ...
  ...
  "BLE Stack"<--]: Receive ATT_WRITE_RSP\nor ATT_ERROR_RSP

"BLE Stack"->ICALL: Parse the msg

activate ICALL

  rnote over "ICALL"
  Send GATT_MSG_EVENT
  to the application layer
  end note

ICALL->App : Notify application
deactivate "ICALL"

App->App: SimpleBLECentral_processStackMsg

rnote over "App"
GATT_MSG_EVENT
end note

App->App: SimpleBLECentral_processGATTMsg

rnote over "App"
ATT_WRITE_RSP
ATT_ERROR_RSP
end note
end

@enduml

Figure 77. Context Diagram of Application using GATT layer.

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.

  1. 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 equation ATT_MTU=MAX_PDU_SIZE-L2CAP_HDR_SIZE

  2. 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.

  3. 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.

  1. Include the header file.

#include "gapgattserver.h"
  1. Initialize the GGS parameters. Code snippet example from simple peripheral.

Listing 122. simple_peripheral.c :: SimplePeripheral_init() - Initialize the GGS parameters.
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);
  1. Initialize the application callbacks with GGS (optional). This notifies the application when any of the characteristics in the GGS have changed.

GGS_RegisterAppCBs(&appGGSCBs);
  1. Add the GGS to the GATT server.

Listing 123. simple_peripheral.c :: SimplePeripheral_init() - Add 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.

  1. 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 the build_config.opt file.)

  1. 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.

  2. 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.

@startuml

participant App.c
participant "Profile A"
participant "Profile B"
participant GATTServApp
participant "GATT Server"

rnote over "Profile A", "Profile B"
  Service A and Service B
  Table initialization
end note

App.c -> "GATT Server" : GGS_AddService()

note right
  GAP Service
end note

group Add GATTServApp to attribute table
    App.c -> GATTServApp : GATTServApp_AddService()
    GATTServApp -> "GATT Server" : RegisterService

    note right
      GAP Service
      GATT Service
    end note
end

App.c -> "Profile A" : ProfileA_AddService()
"Profile A" -> GATTServApp : RegisterService(A)


GATTServApp -> "GATT Server" : RegisterService

note right
  GAP Service
  GATT Service
  Service A
end note

deactivate GATTServApp
deactivate "Profile A"


== Profile B ==


App.c -> "Profile B" : ProfileB_AddService()
"Profile B" -> GATTServApp : RegisterService(B)
GATTServApp -> "GATT Server" : RegisterService


note right
  GAP Service
  GATT Service
  Service A
  Service B
end note
@enduml

Figure 78. Attribute Table Initialization Diagram.

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), or ATT_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.

Listing 124. simple_gatt_profile characteristic 4 pValue pointer define.
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:

Listing 125. Register application callbacks.
 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.

 @startuml
  participant "Service CB \n(simple_gatt_profile.c)" as App
  participant GATTServApp
  participant "BLE Stack"  as BLE

  BLE <--]: Receive Attribute Read Request

  BLE -> GATTServApp : GATT_MSG_EVENT

  Activate "GATTServApp"
   GATTServApp -> GATTServApp : Process Event
   GATTServApp -> GATTServApp : Process Msg "ATT Read Req"

  group status != SUCCESS
    GATTServApp -> BLE : ATT_ErrorRsp
    Deactivate "GATTServApp"
    BLE -->] : Send ATT_ERROR_RSP
  end


  group status == SUCCESS
    GATTServApp -> App : Find Attribute CB
    App -> App : simpleProfile_ReadAttrCB
    rnote over "App"
      Copy the values
    end note
    App -> GATTServApp
    GATTServApp -> BLE : ATT_ReadRsp
    BLE -->] : ATT_READ_RSP
  end

 @enduml

Figure 79. Read Request illustration

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.

 @startuml
  participant "User Define Post Processing" as User
  participant "Service CB \n(simple_gatt_profile.c)" as App
  participant GATTServApp
  participant "BLE Stack"  as BLE

  BLE <--]: Receive Attribute Write Request

  BLE -> GATTServApp : GATT_MSG_EVENT

  Activate "GATTServApp"
   GATTServApp -> GATTServApp : Process Event
   GATTServApp -> GATTServApp : Process Msg "ATT Write Req"

  group status != SUCCESS
    GATTServApp -> BLE : ATT_ErrorRsp
    Deactivate "GATTServApp"
    BLE -->] : Send ATT_ERROR_RSP
  end


  group status == SUCCESS
    GATTServApp -> App : Find Attribute CB
    App -> App : simpleProfile_WriteAttrCB
    rnote over "App"
                       update the value
                                   &
      find user defined application callback
    end note

    GATTServApp -> BLE : ATT_WriteRsp
    BLE -->] : ATT_Write_RSP

    App -> User : SimpleBLEPeripheral_\ncharValueChangeCB()

    User-> User : SimpleBLEPeripheral_\nenqueueMsg()
    rnote over "User"
      SBP_CHAR_CHANGE_EVT
    end note

    User-> User : SimpleBLEPeripheral_\nprocessCharValueChangeEvt()

    rnote over "User"
      Get the new value and
      do whatever you want with it
    end note

  end



 @enduml

Figure 80. Write Request illustration

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.

 @startuml
  participant App
  participant "Profile \n(simple_gatt_profile.c)" as profile
  participant GATTServApp
  participant "BLE Stack"  as BLE

  App -> profile : SetParameter(param, len, value)

  group Invalid operation
    profile -> App : return(bleInvalidRange|\nINVALIDPARAMETER)
  end note


  group Valid operation (correct len etc)

  rnote over "profile"
    Copy and update the values
  end note
  profile -> GATTServApp : GATTServApp_ProcessCharCfg()

  rnote over "GATTServApp"
    Check if the notification has
    already been enabled
  end note

  group Notification is not enabled
    GATTServApp -> profile : return(SUCCESS)
    profile -> App : return(SUCCESS)
  end


  group Notification is enabled
    GATTServApp -> BLE : GATT_Notification()
    BLE -->]: Send notification to client
    BLE->GATTServApp : return(status)
    GATTServApp -> profile : return(status)
    profile -> App : return(SUCCESS)
  end

  end note


 @enduml

Figure 81. Set Profile Parameter

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.

Listing 126. Set characteristic 4 in SimpleProfile_SetParameter().
 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.

  1. Try to allocate memory for the notification or indication payload using GATT_bm_alloc().

  2. 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.

  1. 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.

Listing 127. gattServApp_SendNotiInd().
 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, &noti.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, &noti, authenticated );
14      }
15
16      else // GATT_CLIENT_CFG_INDICATE
17      {
18         status = GATT_Indication( connHandle, (attHandleValueInd_t *)&noti, authenticated, taskId );
19      }
20   }
21
22   if ( status != SUCCESS )
23   {
24      GATT_bm_free( (gattMsg_t *)&noti, 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.

Listing 128. See if GATT server was unable to transmit an ATT response.
 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.

Listing 129. ATT flow control violation.
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.

Listing 130. MTU 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_ReadRsp()

ATT_READ_BLOB_REQ

ATT_ReadBlobRsp()

ATT_READ_BY_TYPE_REQ

GATTServApp_ReadRsp()

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.

 @startuml
  participant "Service CB \n(simple_gatt_profile.c)" as App
  participant GATTServApp
  participant "BLE Stack"  as BLE

  BLE <--]: Receive Attribute Read Request

  BLE -> GATTServApp : GATT_MSG_EVENT

  Activate "GATTServApp"
   GATTServApp -> GATTServApp : Process Event
   GATTServApp -> GATTServApp : Process Msg "ATT Read Req"

  group status != (SUCCESS | BLE_PENDING)
    GATTServApp -> BLE : ATT_ErrorRsp
    Deactivate "GATTServApp"
    BLE -->] : Send ATT_ERROR_RSP
  end


  group status == SUCCESS
    GATTServApp -> App : Find Attribute CB
    App -> App : simpleProfile_ReadAttrCB
    rnote over "App"
      Copy the values
    end note
    App -> GATTServApp
    GATTServApp -> BLE : ATT_ReadRsp
    BLE -->] : ATT_READ_RSP
  end

  group status == BLE_PENDING
    GATTServApp -> App : Find Attribute CB
    App -> App : simpleProfile_ReadAttrCB
    rnote over "App"
      Wait for data
    end note
    App -> GATTServApp
    GATTServApp -> BLE : GATTServApp_ReadRsp
    BLE -->] : ATT_READ_RSP
  end

 @enduml

Figure 82. Delay Read Request Illustration

Note

To utilize the GATT Builder tool, please reference Custom GATT Builder for more information on how to build your own services and characteristics.