Bluetooth Low Energy is a powerful technology, but not always the easiest to understand and use effectively. It is not like classic Bluetooth where you have a predefined set of official profiles to choose from; although there are predefined (a.k.a. "adopted") profiles specified by the Bluetooth SIG, these are just the tip of the iceberg, a small subset of the kind of functionality you can achieve with BLE.
In many (or even most) cases, the best option is to create your own custom profile(s) for your application. This gives you ultimate flexibility, and it doesn't even cost anything. In fact, it can be even easier than using one of the adopted profiles, since you get to define exactly how everything works together rather than conforming your application into something that is already defined. Also, since there is no official generic "serial port profile" in the BLE world like SPP in classic Bluetooth, sometimes a custom implementation is the only option to do what you need.
In order to do this effectively, it is important to understand how a BLE connection works, what roles are played by the devices involved, and how exactly data is transferred from one device to the other over the air. Many terms are used, and they are usually not interchangeable: master, slave, central, peripheral, client, server, advertise, scan, read, write, notify, and indicate all mean different things. Knowing the difference will make it easier to describe and build your BLE application.
Quick Overview
Here's what this article covers:
Master (or "central") devices scan for other devices. Usually, the master is the smartphone/tablet/PC.
Slave (or "peripheral") devices advertise and wait for connections. Usually, the slave is the BGMxxx module of EFR32 device.
Client devices access remote resources over a BLE link using the GATT protocol. Usually, the master is also the client.
Server devices have a local database and access control methods, and provide resources to the remote client. Usually, the slave is also the server.
You can use read, write, notify, or indicate operations to move data between the client and the server.
Read and write operations are requested by the client and the server responds (or acknowledges).
Notify and indicate operations are enabled by the client but initiated by the server, providing a way to push data to the client.
Notifications are unacknowledged, while indications are acknowledged. Notifications are therefore faster, but less reliable.
Master vs. Slave - Connection Roles
One important concept in BLE connectivity is the difference between a master device and a slave device. What do each of these terms imply? First, realize that they are not interchangeable with client/server, which will be explained below. Actually, in the BLE world, the master/slave difference is very easy to define and recognize:
Master / Central - the BLE device which initiates an outgoing connection request to an advertising peripheral device
Slave / Peripheral - the BLE device which accepts an incoming connection request after advertising
These pairs of terms are the only ones in the list above which are actually interchangeable. References to a "master" or "central" device are describing the same thing, and references to a "slave" or "peripheral" device are also each describing the same thing. In the CoreBluetooth APIs provided by Apple for iOS development, the "Central" and "Peripheral" nomenclature is used; you will not generally see references to master or slave devices in this context.
The BLE specification does not limit the number of slaves a master may connect to, but there is always a practical limitation, especially on small embedded modules. Our 2.8.1 BLE stack can support up to 8 simultaneous connections device, when properly configured (the default is 4). Our stack supports dual-topology and multi-master connections (which are part of the Bluetooth 4.1 spec) which means that a device can be simultaneously a master and a slave and it can also connect to multiple masters a slave device. The connection limit applies to the total number of connections regardless of the role.
The connection role, whether a device is a master or slave, is defined the moment the connection is made. Our stack is capable of acting either as a master or as a slave device. If a device is operating as a slave, it needs to advertise (accomplished in our BLE stack with the gecko_cmd_le_gap_start_advertising command); if it is operating as a master, it will optionally scan for devices (accomplished in our stack with gecko_cmd_le_gap_start_discovery) and initiate a connection request to another device (accomplished in our stack with gecko_cmd_le_gap_connect).
Client vs. Server - GATT Functionality
Another important concept in a BLE design is the difference between a GATT server and a GATT client (where GATT means Generic ATTribute profile). These roles are not mutually exclusive, though typically your device will only be one or the other. Which role(s) your device takes depends on how you need it to work. Here is a basic summary of each kind of functionality:
GATT client - a device which accesses data on the remote GATT server via read, write, notify, or indicate operations
GATT server - a device which stores data locally and provides data access methods to a remote GATT client
Unlike the master/slave distinction defined previously, it is easy to see that one device might actually be both of these things at the same time, based on how your application defines the data structure and flow for each side of the connection. While it is most common for the slave (peripheral) device to be the GATT server and the master (center) device to be the GATT client, this is not required. The GATT functionality of a device is logically separate from the master/slave role. The master/slave roles control how the BLE radio connection is managed, and the client/server roles are dictated by the storage and flow of data.
Most of our example projects in the SDK archive and online implement slave (peripheral) devices designed to be GATT servers. These are easy to test with our Blue Gecko App (available for Android and iOS). However, there are also a few examples which implement the master (central) end of the connection, designed to be GATT clients.
Receive vs. Transmit - Moving Data
Now that we have established the difference between a master and slave device, and a GATT client and GATT server, how do we actually define the GATT structure, and how do we use it to move data from Device A to Device B, whether that means server-to-client or client-to-server? Both directions are easily possible, of course.
In BLE projects built using our Bluetooth SDK, the GATT structure can be configured using the built-in tool from Simplicity Studio, called the Visual GATT Editor. This can be found on the General or on the Bluetooth Configurator tab of the Application Builder. The configuration file that stores the GATT structure beside other configurations can be found inside the project-folder denoted by .isc-extension. Once you press Generate in the Visual GATT Editor, the gatt_db.c/.h and the gatt.xml files are generated.
The structure and flow of data is always defined on the GATT server, and the client simply gets to make use of whatever is exposed by the server.
A GATT database implements one or more profiles, and each profile is made up of one or more services, and each service is made up of one or more characteristics. For example, in outline form:
Profile 1
Service A
Characteristic a
Characteristic b
Characteristic c
Service B
Characteristic d
Characteristic e
Profile 2
Service C
Characteristic f
Characteristic g
Characteristic h
Service D
Characteristic i
Characteristic j
Profile 3
Service E
Characteristic k
Characteristic l
Characteristic m
Service F
Characteristic n
Characteristic o
You can implement as many profiles, services, and characteristics as you need. These may be entirely customized, in which case they would use your own 128-bit UUIDs generated by the Visual GATT editor. Or, you can create a project which implements adopted specifications by referencing the Bluetooth SIG's online definitions of profiles, services, and characteristics. (If you do this, it usually makes the most sense to start at the profile level and drill down from there, since the full list of characteristics includes many things that will undoubtedly be irrelevant to your design.) You can combine these two as well, using some adopted profiles/services and some of your own proprietary ones.
Every BLE device acting as a GATT server must implement the official Generic Access service. This includes two mandatory characteristics: Device Name and Appearance. These are similar to the Friendly Name and Class of Device values used by classic Bluetooth. Here is an example definition of an absolutely minimal GATT definition as shown in the Visual GATT editor:
This alone is enough to work with, but it won't let you do anything useful other than tell the GATT client who and what you are. We still need to explore basic data transfer methods that we can use. First though, one minor clarification between attributes and characteristics.
Attributes and Characteristics
You may occasionally hear or see the terms "attribute" and "characteristic" used interchangeably, and while this isn't entirely wrong, it isn't totally accurate and it can be confusing. Remember that a service is made up of one or more characteristics. However, one single characteristic--generally the most specific level down to which we define our GATT structure--may be comprised of many different attributes.
Each attribute is given a unique numerical handle which the GATT client may use to reference, access, and modify it. Every characteristic has one main attribute which allows access to the actual value stored in the database for that characteristic. When you read about "writing a value to this characteristic" or "reading that characteristic's value," the read/write operations are really being done to the main data attribute.
Some other related attributes are read-only (such as a Characteristic User Description attribute), some control the behavior of the characteristic (such as the Client Characteristic Configuration attribute which is used to enable notify or indicate operations). Our BLE stack and SDK tools generate these as necessary based on the settings configured in the Visual GATT Editor.
Every attribute has a UUID. These may be either 16 bits (e.g. "180a") or 128 bits (e.g. "e7add780-b042-4876-aae1-112855353cc1"). All 16-bit UUIDs are defined by the Bluetooth SIG and are known as adopted UUIDs. All 128-bit UUIDs are custom and may be used for any purpose without approval from the Bluetooth SIG. Two very common 16-bit UUIDs that you will see are 2901, the Characteristic User Description attribute (defined in the User description field of the GATT Editor), and 2902, the Client Characteristic Configuration attribute (created by our SDK when either "notify" or "indicate" is enabled on a characteristic).
One important note is that some attribute UUIDs do not technically need to be unique. Their handles are always unique, but the UUIDs occasionally overlap. For example, every Client Characteristic Configuration attribute has a UUID of 0x2902, even though there may be a dozen of them in a single GATT database. You should always give your own custom characteristics fully unique UUIDs, but don't be alarmed if you are testing out your prototype with BGTool, you perform a Descriptor Discovery operation, and suddenly see multiple instances of the same UUID for certain things. This is normal.
Data Transfer Methods
There are four basic operations for moving data in BLE: read, write, notify, and indicate. The BLE protocol specification allows that the maximum data payload size for these operations is 247 bytes, or in the case of read operations, 249 bytes. BLE is built for low power consumption, for infrequent short-burst data transmissions. Sending lots of data is possible, but usually ends up being less efficient than classic Bluetooth when trying to achieve maximum throughput. Here are a few general guidelines about what kind of data transfer you will need to use:
If the client needs to send data to the server, use write.
If the client needs to get data from the server on-demand (i.e. polling), use read.
If the server needs to send data to the client without the client requesting it first, use notify or indicate. (The client must subscribe to these updates before any data will be transferred.)
The server cannot pull data from the client directly. If this is necessary, the server must use notify or indicate to send pre-arranged request data to the client, and then the client may follow up with a write operation to send back whatever data that the server needs.
The above four BLE data transfer operations are described here. Commands which you must send are shown separately from the events which the stack generates. Complete API reference can be found for example through Simplicity Studio Launcher on the Documentation tab.
Read
This operation is requested by the GATT client on a specific attribute exposed by the GATT server, and the server then responds with the requested value. In our BLE stack, these API methods are typically involved in read operations:
gecko_cmd_gatt_read_characteristic_value command
Reads a remote attribute's value with the given handle. A single gecko_evt_gatt_characteristic_value event is generated if the characteristic value fits in one ATT PDU. Otherwise more than one gecko_evt_gatt_characteristic_value events are generated because the firmware will automatically use the "read long" GATT procedure. This is the most common method used by a GATT client to read individual attribute values.
gecko_cmd_gatt_read_characteristic_value_by_uuid command
Reads the characteristic value of a service from a remote GATT database by giving the UUID of the characteristic and the handle of the service containing this characteristic. A gecko_evt_gatt_characteristic_value event is generated if the characteristic value fits in one ATT PDU. Otherwise more than one gecko_evt_gatt_characteristic_value events are generated because the firmware will automatically use the "read long" GATT procedure.
gecko_cmd_gatt_read_multiple_characteristic_values command
This command can be used to read the values of multiple characteristics from a remote GATT database at once. gecko_evt_gatt_characteristic_value events are generated as the values are returned by the remote GATT server.
gecko_evt_gatt_server_user_read_request event
Generated on the GATT server when a client requests reading data from a characteristic whose "type" value is set to "user". Normally, this operation is handled by the stack automatically, and the characteristic value stored in RAM right on the module is sent back to the client. But with "user" characteristics, this data storage and retrieval is entirely up to you. The stack simply lets you know when it has been requested, and then you must get it (or generate it), then send it back (or not) in whatever format is desired. This can be useful for immediate I/O status reads, for example, so that instead of maintaining a value in memory or polling all the time so it is kept "fresh," you can instead just wait for the request and read the I/O status on the spot when it comes in. (ONLY applies if Value type="user" is set in the GATT Editor for the given characteristic)
gecko_cmd_gatt_server_send_user_read_response command
Used to send back the desired data when a gecko_evt_gatt_server_user_read_request is generated by a client's request on a "user" characteristic. This command takes a result code and the actual value, if any, to send back. (ONLY applies if Value type="user" is set in the GATT editor for the given characteristic)
Write
This operation is requested by the GATT client on a specific attribute exposed by the GATT server, and a new value to write is provided at the same time. The server then stores the new value and (optionally) acknowledges the write operation back to the client. In our BLE stack, these API methods are typically involved in write operations:
gecko_cmd_gatt_write_characteristic_value command
Writes a remote attribute's value on a GATT server. This performs an acknowledged write operation, so the server will respond when the value has been successfully written. If the given value does not fit in one ATT PDU, "write long" GATT procedure is used automatically. This is the most common method used by a GATT client to write individual attribute values.
gecko_evt_gatt_procedure_completed event
This event indicates that the current GATT procedure has been completed successfully or that it has failed with an error. All GATT commands excluding gecko_cmd_gatt_write_characteristic_value_without_response and gecko_cmd_gatt_send_characteristic_confirmation will trigger this event, so the application must wait for this event before issuing another GATT command (excluding the two aforementioned exceptions). This event is useful for controlling program flow based on whether a critical operation has finished yet or not.
gecko_cmd_gatt_write_characteristic_value_without_response command
Writes a remote attribute's value on a GATT server. This is exactly the same as the above command except that it is unacknowledged. There will be no response upon completion. Like the "notify" operation, this means that it is faster (multiple unacknowledged writes may be performed within a single connection interval), but less reliable than acknowledged writes.
gecko_cmd_gatt_prepare_characteristic_value_write command
Can be used to add a characteristic value to the write queue of a remote GATT server. This command can be used in cases where very long attributes need to be written, or a set of values needs to be written atomically. At most 245 (ATT_MTU - 5) amount of data can be sent once. The gecko_evt_gatt_procedure_completed event occurs when the preparation is done. In all cases where the amount of data to transfer fits into the BGAPI payload the command gecko_cmd_gatt_write_characteristic_value is recommended for writing long values since it transparently performs the "prepare_write" and "execute_write" commands.
Here is an example of the flow of queueing writes:
gecko_cmd_gatt_prepare_characteristic_value_write
wait for gecko_cmd_gatt_prepare_characteristic_value_write response
wait for gecko_evt_gatt_procedure_completed event
gecko_cmd_gatt_prepare_characteristic_value_write
wait for gecko_cmd_gatt_prepare_characteristic_value_write response
wait for gecko_cmd_gatt_execute_characteristic_value_write response
wait for gecko_evt_gatt_procedure_completed event
gecko_cmd_gatt_execute_characteristic_value_write command
Executes (argument=1) or cancels (argument=0) any pending write operations queued with gecko_cmd_gatt_prepare_characteristic_value_write. The stack handles sending the queued writes in multiple packets automatically when you run this command, and it will take as many connection intervals as necessary. The gecko_evt_gatt_procedure_completed event occurs when the execution is done. On the server side, this looks like a rapid series of write operations, and is handled the same way (acknowledging each one).
gecko_evt_gatt_server_attribute_value event
Triggered on the GATT server when the client writes a new value to a particular characteristic. This is the event you can use to tell when a characteristic has been updated, and then respond accordingly (parse the new value for command data, set I/O pins based on the stored data, close the connection, begin reading ADC measurements, etc.).
gecko_evt_gatt_server_user_write_request event
If type="user" is set for a given characteristic, then this event will be generated instead of gecko_evt_gatt_server_attribute_value. The new written value from the client will be provided as one of the parameters of this event, but the value itself will not be stored in RAM automatically by the stack. It is up to you to store or otherwise process the value, and acknowledge as necessary. Parameter att_opcode describes which GATT procedure was used to change the value.
gecko_cmd_gatt_server_send_user_write_response command
Used to send back an appropriate acknowledgement result code when a gecko_evt_gatt_server_user_write_request event is generated by a client's write operation on a "user" characteristic. If this manual acknowledgement is necessary, then you should typically call this command in the same code as the event handler for the related gecko_evt_gatt_server_user_write_request event. (ONLY applies if Value type="user" is set in the GATT editor for the given characteristic)
Notify
This operation is initiated by the server when a new value is written to a notify-enabled characteristic. If the client has subscribed to notifications on that characteristic, then the new value is pushed to the client when it is written. Notifications are not acknowledged, hence you may send more than one notification in a single connection interval, which can be helpful maximizing throughput. Notifications cannot be enabled by the server; they must be enabled by the client in order for the actual data transmission to occur. In our BLE stack, these API methods are typically involved in notify operations:
gecko_cmd_gatt_server_write_attribute_value command
Writes a local attribute's value on the GATT server. Writing the value of a characteristic of the local GATT database will not trigger notifications or indications to the remote GATT client in case such characteristic has property of indicate or notify and the client has enabled notification or indication. Notifications and indications are sent to the remote GATT client using gecko_cmd_gatt_server_send_characteristic_notification command.
gecko_evt_gatt_server_characteristic_status event
This event indicates either that a local Client Characteristic Configuration descriptor has been changed by the remote GATT client, or that a confirmation from the remote GATT client was received upon a successful reception of the indication. Status_flags parameter tells whether Client Characteristic Configuration was changed or if confirmation was received (0x02). Client_config_flags has the new values for the configuration. If the updated config flag equals 1, then notifications are enabled. If it equals 2, then indications are enabled. If it equals 0, then neither are enabled.
gecko_evt_gatt_characteristic_value event
This event indicates that the value of a characteristic in the remote GATT server was received. This event is triggered as a result of several commands e.g. gecko_cmd_gatt_read_characteristic_value and gecko_cmd_gatt_read_multiple_characteristic_values; and when the remote GATT server sends indications or notifications after enabling notifications with gatt_set_characteristic_notification. The parameter att_opcode reveals which type of GATT transaction triggered this event. In particular, if the att_opcode type is handle_value_indication (0x1d), the application needs to confirm the indication with gecko_cmd_gatt_send_characteristic_confirmation. This event is how the client knows it has received an updated value. The pushed data will be made available as part of the event's parameters.
Indicate
An indicate operation is identical to a notify operation except that indications are acknowledged, while notifications are not. This increases reliability at the expense of speed. In our BLE stack, these API methods are typically involved in indicate operations:
gecko_cmd_gatt_server_write_attribute_value command
Writes a local attribute's value on the GATT server. Writing the value of a characteristic of the local GATT database will not trigger notifications or indications to the remote GATT client in case such characteristic has property of indicate or notify and the client has enabled notification or indication. Notifications and indications are sent to the remote GATT client using gecko_cmd_gatt_server_send_characteristic_notification command. Remember that indications are acknowledged, so you may do this only once within a given connection interval. It will be followed up by the gecko_evt_gatt_server_characteristic_status event with status_flags = 0x2 when the acknowledgement comes back from the GATT client.
gecko_evt_gatt_server_characteristic_status event
This event indicates either that a local Client Characteristic Configuration descriptor has been changed by the remote GATT client, or that a confirmation from the remote GATT client was received upon a successful reception of the indication. Status_flags parameter tells whether Client Characteristic Configuration was changed or if confirmation was received (0x02). Client_config_flags has the new values for the configuration. If the updated config flag equals 1, then notifications are enabled. If it equals 2, then indications are enabled. If it equals 0, then neither are enabled.
gecko_evt_gatt_characteristic_value event
This event indicates that the value of a characteristic in the remote GATT server was received. This event is triggered as a result of several commands e.g. gecko_cmd_gatt_read_characteristic_value and gecko_cmd_gatt_read_multiple_characteristic_values; and when the remote GATT server sends indications or notifications after enabling notifications with gatt_set_characteristic_notification. The parameter att_opcode reveals which type of GATT transaction triggered this event. In particular, if the att_opcode type is handle_value_indication (0x1d), the application needs to confirm the indication with gecko_cmd_gatt_send_characteristic_confirmation. This event is how the client knows it has received an updated value. The pushed data will be made available as part of the event's parameters.
gecko_cmd_gatt_send_characteristic_confirmation command
Sends an indication confirmation from the GATT client back to the remote GATT server, after an indicated value has arrived at the client. The gecko_evt_gatt_characteristic_value carries the att_opcode containing handle_value_indication (0x1d) which reveals that an indication has been received and this must be confirmed with this command. Confirmation needs to be sent within 30 seconds, otherwise the GATT transactions between the client and the server are discontinued. Sometimes there are specific data flow control triggers you need to deal with very carefully. Controlling precisely when the confirmation occurs and what happens before and after it may be valuable.
gecko_evt_gatt_server_characteristic_status
This event indicates either that a local Client Characteristic Configuration descriptor has been changed by the remote GATT client, or that a confirmation from the remote GATT client was received upon a successful reception of the indication. Very often, this event is ignored, but as mentioned in the previous item, sometimes it is valuable to know precisely when this occurs for data flow control purposes.
Typically, the GATT server functionality is provided by one side and the client functionality is entirely on the other side, but it could be that both sides provide both kinds of functionality. This does not usually pose any advantages for a well-designed and efficient BLE project — it usually complicates the implementation needlessly — so we will not go into it here.
[Deprecated] KBA_BT_0102: BLE Basics (master/slave, GATT client/server, data RX/TX)
Note: This KBA has been marked as deprecated. A more updated KBA can be found here:
Master and Slave Roles and GATT Server and Client Roles
Bluetooth Low Energy is a powerful technology, but not always the easiest to understand and use effectively. It is not like classic Bluetooth where you have a predefined set of official profiles to choose from; although there are predefined (a.k.a. "adopted") profiles specified by the Bluetooth SIG, these are just the tip of the iceberg, a small subset of the kind of functionality you can achieve with BLE.
In many (or even most) cases, the best option is to create your own custom profile(s) for your application. This gives you ultimate flexibility, and it doesn't even cost anything. In fact, it can be even easier than using one of the adopted profiles, since you get to define exactly how everything works together rather than conforming your application into something that is already defined. Also, since there is no official generic "serial port profile" in the BLE world like SPP in classic Bluetooth, sometimes a custom implementation is the only option to do what you need.
In order to do this effectively, it is important to understand how a BLE connection works, what roles are played by the devices involved, and how exactly data is transferred from one device to the other over the air. Many terms are used, and they are usually not interchangeable: master, slave, central, peripheral, client, server, advertise, scan, read, write, notify, and indicate all mean different things. Knowing the difference will make it easier to describe and build your BLE application.
Quick Overview
Here's what this article covers:
Master vs. Slave - Connection Roles
One important concept in BLE connectivity is the difference between a master device and a slave device. What do each of these terms imply? First, realize that they are not interchangeable with client/server, which will be explained below. Actually, in the BLE world, the master/slave difference is very easy to define and recognize:
These pairs of terms are the only ones in the list above which are actually interchangeable. References to a "master" or "central" device are describing the same thing, and references to a "slave" or "peripheral" device are also each describing the same thing. In the CoreBluetooth APIs provided by Apple for iOS development, the "Central" and "Peripheral" nomenclature is used; you will not generally see references to master or slave devices in this context.
The BLE specification does not limit the number of slaves a master may connect to, but there is always a practical limitation, especially on small embedded modules. Our 2.8.1 BLE stack can support up to 8 simultaneous connections device, when properly configured (the default is 4). Our stack supports dual-topology and multi-master connections (which are part of the Bluetooth 4.1 spec) which means that a device can be simultaneously a master and a slave and it can also connect to multiple masters a slave device. The connection limit applies to the total number of connections regardless of the role.
The connection role, whether a device is a master or slave, is defined the moment the connection is made. Our stack is capable of acting either as a master or as a slave device. If a device is operating as a slave, it needs to advertise (accomplished in our BLE stack with the gecko_cmd_le_gap_start_advertising command); if it is operating as a master, it will optionally scan for devices (accomplished in our stack with gecko_cmd_le_gap_start_discovery) and initiate a connection request to another device (accomplished in our stack with gecko_cmd_le_gap_connect).
Client vs. Server - GATT Functionality
Another important concept in a BLE design is the difference between a GATT server and a GATT client (where GATT means Generic ATTribute profile). These roles are not mutually exclusive, though typically your device will only be one or the other. Which role(s) your device takes depends on how you need it to work. Here is a basic summary of each kind of functionality:
Unlike the master/slave distinction defined previously, it is easy to see that one device might actually be both of these things at the same time, based on how your application defines the data structure and flow for each side of the connection. While it is most common for the slave (peripheral) device to be the GATT server and the master (center) device to be the GATT client, this is not required. The GATT functionality of a device is logically separate from the master/slave role. The master/slave roles control how the BLE radio connection is managed, and the client/server roles are dictated by the storage and flow of data.
Most of our example projects in the SDK archive and online implement slave (peripheral) devices designed to be GATT servers. These are easy to test with our Blue Gecko App (available for Android and iOS). However, there are also a few examples which implement the master (central) end of the connection, designed to be GATT clients.
Receive vs. Transmit - Moving Data
Now that we have established the difference between a master and slave device, and a GATT client and GATT server, how do we actually define the GATT structure, and how do we use it to move data from Device A to Device B, whether that means server-to-client or client-to-server? Both directions are easily possible, of course.
In BLE projects built using our Bluetooth SDK, the GATT structure can be configured using the built-in tool from Simplicity Studio, called the Visual GATT Editor. This can be found on the General or on the Bluetooth Configurator tab of the Application Builder. The configuration file that stores the GATT structure beside other configurations can be found inside the project-folder denoted by .isc-extension. Once you press Generate in the Visual GATT Editor, the gatt_db.c/.h and the gatt.xml files are generated.
The structure and flow of data is always defined on the GATT server, and the client simply gets to make use of whatever is exposed by the server.
If you are using the IAR Embedded Workbench refer to Profile Toolkit Developer Guide.
GATT structure
A GATT database implements one or more profiles, and each profile is made up of one or more services, and each service is made up of one or more characteristics. For example, in outline form:
You can implement as many profiles, services, and characteristics as you need. These may be entirely customized, in which case they would use your own 128-bit UUIDs generated by the Visual GATT editor. Or, you can create a project which implements adopted specifications by referencing the Bluetooth SIG's online definitions of profiles, services, and characteristics. (If you do this, it usually makes the most sense to start at the profile level and drill down from there, since the full list of characteristics includes many things that will undoubtedly be irrelevant to your design.) You can combine these two as well, using some adopted profiles/services and some of your own proprietary ones.
Every BLE device acting as a GATT server must implement the official Generic Access service. This includes two mandatory characteristics: Device Name and Appearance. These are similar to the Friendly Name and Class of Device values used by classic Bluetooth. Here is an example definition of an absolutely minimal GATT definition as shown in the Visual GATT editor:
This alone is enough to work with, but it won't let you do anything useful other than tell the GATT client who and what you are. We still need to explore basic data transfer methods that we can use. First though, one minor clarification between attributes and characteristics.
Attributes and Characteristics
You may occasionally hear or see the terms "attribute" and "characteristic" used interchangeably, and while this isn't entirely wrong, it isn't totally accurate and it can be confusing. Remember that a service is made up of one or more characteristics. However, one single characteristic--generally the most specific level down to which we define our GATT structure--may be comprised of many different attributes.
Each attribute is given a unique numerical handle which the GATT client may use to reference, access, and modify it. Every characteristic has one main attribute which allows access to the actual value stored in the database for that characteristic. When you read about "writing a value to this characteristic" or "reading that characteristic's value," the read/write operations are really being done to the main data attribute.
Some other related attributes are read-only (such as a Characteristic User Description attribute), some control the behavior of the characteristic (such as the Client Characteristic Configuration attribute which is used to enable notify or indicate operations). Our BLE stack and SDK tools generate these as necessary based on the settings configured in the Visual GATT Editor.
Every attribute has a UUID. These may be either 16 bits (e.g. "180a") or 128 bits (e.g. "e7add780-b042-4876-aae1-112855353cc1"). All 16-bit UUIDs are defined by the Bluetooth SIG and are known as adopted UUIDs. All 128-bit UUIDs are custom and may be used for any purpose without approval from the Bluetooth SIG. Two very common 16-bit UUIDs that you will see are 2901, the Characteristic User Description attribute (defined in the User description field of the GATT Editor), and 2902, the Client Characteristic Configuration attribute (created by our SDK when either "notify" or "indicate" is enabled on a characteristic).
One important note is that some attribute UUIDs do not technically need to be unique. Their handles are always unique, but the UUIDs occasionally overlap. For example, every Client Characteristic Configuration attribute has a UUID of 0x2902, even though there may be a dozen of them in a single GATT database. You should always give your own custom characteristics fully unique UUIDs, but don't be alarmed if you are testing out your prototype with BGTool, you perform a Descriptor Discovery operation, and suddenly see multiple instances of the same UUID for certain things. This is normal.
Data Transfer Methods
There are four basic operations for moving data in BLE: read, write, notify, and indicate. The BLE protocol specification allows that the maximum data payload size for these operations is 247 bytes, or in the case of read operations, 249 bytes. BLE is built for low power consumption, for infrequent short-burst data transmissions. Sending lots of data is possible, but usually ends up being less efficient than classic Bluetooth when trying to achieve maximum throughput. Here are a few general guidelines about what kind of data transfer you will need to use:
The above four BLE data transfer operations are described here. Commands which you must send are shown separately from the events which the stack generates. Complete API reference can be found for example through Simplicity Studio Launcher on the Documentation tab.
Read
This operation is requested by the GATT client on a specific attribute exposed by the GATT server, and the server then responds with the requested value. In our BLE stack, these API methods are typically involved in read operations:
Reads a remote attribute's value with the given handle. A single gecko_evt_gatt_characteristic_value event is generated if the characteristic value fits in one ATT PDU. Otherwise more than one gecko_evt_gatt_characteristic_value events are generated because the firmware will automatically use the "read long" GATT procedure. This is the most common method used by a GATT client to read individual attribute values.
Reads the characteristic value of a service from a remote GATT database by giving the UUID of the characteristic and the handle of the service containing this characteristic. A gecko_evt_gatt_characteristic_value event is generated if the characteristic value fits in one ATT PDU. Otherwise more than one gecko_evt_gatt_characteristic_value events are generated because the firmware will automatically use the "read long" GATT procedure.
This command can be used to read the values of multiple characteristics from a remote GATT database at once. gecko_evt_gatt_characteristic_value events are generated as the values are returned by the remote GATT server.
Generated on the GATT server when a client requests reading data from a characteristic whose "type" value is set to "user". Normally, this operation is handled by the stack automatically, and the characteristic value stored in RAM right on the module is sent back to the client. But with "user" characteristics, this data storage and retrieval is entirely up to you. The stack simply lets you know when it has been requested, and then you must get it (or generate it), then send it back (or not) in whatever format is desired. This can be useful for immediate I/O status reads, for example, so that instead of maintaining a value in memory or polling all the time so it is kept "fresh," you can instead just wait for the request and read the I/O status on the spot when it comes in. (ONLY applies if Value type="user" is set in the GATT Editor for the given characteristic)
Used to send back the desired data when a gecko_evt_gatt_server_user_read_request is generated by a client's request on a "user" characteristic. This command takes a result code and the actual value, if any, to send back. (ONLY applies if Value type="user" is set in the GATT editor for the given characteristic)
Write
This operation is requested by the GATT client on a specific attribute exposed by the GATT server, and a new value to write is provided at the same time. The server then stores the new value and (optionally) acknowledges the write operation back to the client. In our BLE stack, these API methods are typically involved in write operations:
Writes a remote attribute's value on a GATT server. This performs an acknowledged write operation, so the server will respond when the value has been successfully written. If the given value does not fit in one ATT PDU, "write long" GATT procedure is used automatically. This is the most common method used by a GATT client to write individual attribute values.
This event indicates that the current GATT procedure has been completed successfully or that it has failed with an error. All GATT commands excluding gecko_cmd_gatt_write_characteristic_value_without_response and gecko_cmd_gatt_send_characteristic_confirmation will trigger this event, so the application must wait for this event before issuing another GATT command (excluding the two aforementioned exceptions). This event is useful for controlling program flow based on whether a critical operation has finished yet or not.
Writes a remote attribute's value on a GATT server. This is exactly the same as the above command except that it is unacknowledged. There will be no response upon completion. Like the "notify" operation, this means that it is faster (multiple unacknowledged writes may be performed within a single connection interval), but less reliable than acknowledged writes.
Can be used to add a characteristic value to the write queue of a remote GATT server. This command can be used in cases where very long attributes need to be written, or a set of values needs to be written atomically. At most 245 (ATT_MTU - 5) amount of data can be sent once. The gecko_evt_gatt_procedure_completed event occurs when the preparation is done. In all cases where the amount of data to transfer fits into the BGAPI payload the command gecko_cmd_gatt_write_characteristic_value is recommended for writing long values since it transparently performs the "prepare_write" and "execute_write" commands.
Here is an example of the flow of queueing writes:
Executes (argument=1) or cancels (argument=0) any pending write operations queued with gecko_cmd_gatt_prepare_characteristic_value_write. The stack handles sending the queued writes in multiple packets automatically when you run this command, and it will take as many connection intervals as necessary. The gecko_evt_gatt_procedure_completed event occurs when the execution is done. On the server side, this looks like a rapid series of write operations, and is handled the same way (acknowledging each one).
Triggered on the GATT server when the client writes a new value to a particular characteristic. This is the event you can use to tell when a characteristic has been updated, and then respond accordingly (parse the new value for command data, set I/O pins based on the stored data, close the connection, begin reading ADC measurements, etc.).
If type="user" is set for a given characteristic, then this event will be generated instead of gecko_evt_gatt_server_attribute_value. The new written value from the client will be provided as one of the parameters of this event, but the value itself will not be stored in RAM automatically by the stack. It is up to you to store or otherwise process the value, and acknowledge as necessary. Parameter att_opcode describes which GATT procedure was used to change the value.
Used to send back an appropriate acknowledgement result code when a gecko_evt_gatt_server_user_write_request event is generated by a client's write operation on a "user" characteristic. If this manual acknowledgement is necessary, then you should typically call this command in the same code as the event handler for the related gecko_evt_gatt_server_user_write_request event. (ONLY applies if Value type="user" is set in the GATT editor for the given characteristic)
Notify
This operation is initiated by the server when a new value is written to a notify-enabled characteristic. If the client has subscribed to notifications on that characteristic, then the new value is pushed to the client when it is written. Notifications are not acknowledged, hence you may send more than one notification in a single connection interval, which can be helpful maximizing throughput. Notifications cannot be enabled by the server; they must be enabled by the client in order for the actual data transmission to occur. In our BLE stack, these API methods are typically involved in notify operations:
Writes a local attribute's value on the GATT server. Writing the value of a characteristic of the local GATT database will not trigger notifications or indications to the remote GATT client in case such characteristic has property of indicate or notify and the client has enabled notification or indication. Notifications and indications are sent to the remote GATT client using gecko_cmd_gatt_server_send_characteristic_notification command.
This event indicates either that a local Client Characteristic Configuration descriptor has been changed by the remote GATT client, or that a confirmation from the remote GATT client was received upon a successful reception of the indication. Status_flags parameter tells whether Client Characteristic Configuration was changed or if confirmation was received (0x02). Client_config_flags has the new values for the configuration. If the updated config flag equals 1, then notifications are enabled. If it equals 2, then indications are enabled. If it equals 0, then neither are enabled.
This event indicates that the value of a characteristic in the remote GATT server was received. This event is triggered as a result of several commands e.g. gecko_cmd_gatt_read_characteristic_value and gecko_cmd_gatt_read_multiple_characteristic_values; and when the remote GATT server sends indications or notifications after enabling notifications with gatt_set_characteristic_notification. The parameter att_opcode reveals which type of GATT transaction triggered this event. In particular, if the att_opcode type is handle_value_indication (0x1d), the application needs to confirm the indication with gecko_cmd_gatt_send_characteristic_confirmation. This event is how the client knows it has received an updated value. The pushed data will be made available as part of the event's parameters.
Indicate
An indicate operation is identical to a notify operation except that indications are acknowledged, while notifications are not. This increases reliability at the expense of speed. In our BLE stack, these API methods are typically involved in indicate operations:
Writes a local attribute's value on the GATT server. Writing the value of a characteristic of the local GATT database will not trigger notifications or indications to the remote GATT client in case such characteristic has property of indicate or notify and the client has enabled notification or indication. Notifications and indications are sent to the remote GATT client using gecko_cmd_gatt_server_send_characteristic_notification command. Remember that indications are acknowledged, so you may do this only once within a given connection interval. It will be followed up by the gecko_evt_gatt_server_characteristic_status event with status_flags = 0x2 when the acknowledgement comes back from the GATT client.
This event indicates either that a local Client Characteristic Configuration descriptor has been changed by the remote GATT client, or that a confirmation from the remote GATT client was received upon a successful reception of the indication. Status_flags parameter tells whether Client Characteristic Configuration was changed or if confirmation was received (0x02). Client_config_flags has the new values for the configuration. If the updated config flag equals 1, then notifications are enabled. If it equals 2, then indications are enabled. If it equals 0, then neither are enabled.
This event indicates that the value of a characteristic in the remote GATT server was received. This event is triggered as a result of several commands e.g. gecko_cmd_gatt_read_characteristic_value and gecko_cmd_gatt_read_multiple_characteristic_values; and when the remote GATT server sends indications or notifications after enabling notifications with gatt_set_characteristic_notification. The parameter att_opcode reveals which type of GATT transaction triggered this event. In particular, if the att_opcode type is handle_value_indication (0x1d), the application needs to confirm the indication with gecko_cmd_gatt_send_characteristic_confirmation. This event is how the client knows it has received an updated value. The pushed data will be made available as part of the event's parameters.
Sends an indication confirmation from the GATT client back to the remote GATT server, after an indicated value has arrived at the client. The gecko_evt_gatt_characteristic_value carries the att_opcode containing handle_value_indication (0x1d) which reveals that an indication has been received and this must be confirmed with this command. Confirmation needs to be sent within 30 seconds, otherwise the GATT transactions between the client and the server are discontinued. Sometimes there are specific data flow control triggers you need to deal with very carefully. Controlling precisely when the confirmation occurs and what happens before and after it may be valuable.
This event indicates either that a local Client Characteristic Configuration descriptor has been changed by the remote GATT client, or that a confirmation from the remote GATT client was received upon a successful reception of the indication. Very often, this event is ignored, but as mentioned in the previous item, sometimes it is valuable to know precisely when this occurs for data flow control purposes.
Typically, the GATT server functionality is provided by one side and the client functionality is entirely on the other side, but it could be that both sides provide both kinds of functionality. This does not usually pose any advantages for a well-designed and efficient BLE project — it usually complicates the implementation needlessly — so we will not go into it here.
Further reading
Bluetooth Software API Reference Manual
Bluetooth Knowledge Base Article List