GATT — All 11 Features & Procedures
Chapter 13 Part 2 of 2 · Table 13.2 Complete · Service Discovery · Characteristic Read/Write · Notify/Indicate · Descriptor Operations · BlueZ Examples
SEO Keywords:
13.5 — Configured Broadcast
A client can ask the server to include certain characteristic values inside its BLE advertising packets. This is done by setting the broadcast bit in the Server Characteristic Configuration Descriptor — a descriptor associated with that characteristic. Once set, the server includes that characteristic’s value in its advertising data so nearby devices can read it without even connecting.
Example use case: a thermometer broadcasting current temperature in its advertising packets. Any phone nearby can read the temperature from the advertisement without needing to connect, saving radio time for both devices.
13.6 — GATT Features (Table 13.2)
The client negotiates the ATT_MTU size at the start of the connection. Sent only once. Sets the maximum PDU payload for all subsequent ATT operations. Default is 23 octets. See Chapter 12 for the full Exchange MTU walkthrough.
Discover All: Uses Read By Group Type Request (UUID = 0x2800) iteratively, starting from handle 0x0001 and repeating until an Error Response arrives. Returns all services with their handle ranges.
By UUID: Uses Find By Type Value Request (UUID = 0x2800, value = specific service UUID) to find only instances of one particular service. Useful when the client knows exactly which service it needs without wanting to walk the entire database.
/* Discover all primary services: */
gatttool -b AA:BB:CC:DD:EE:FF --primary
/* Discover only Heart Rate Service: */
gatttool -b AA:BB:CC:DD:EE:FF --primary --uuid=0x180D
/* With bluetoothctl: */
[AA:BB:CC:DD:EE:FF]# menu gatt
[AA:BB:CC:DD:EE:FF]# list-attributes
A service may reference (include) other services using Include Definitions. This procedure finds those references. The client uses Read By Type Request with UUID = 0x2802 (Include) within the handle range of the service being examined. The response reveals which other services this service depends on.
For example, if the Heart Rate Service includes the Device Information Service, this procedure will reveal that relationship. The client can then read the Device Information Service directly once it has its start/end handles from the Include Declaration.
Once the service handle range is known, the client reads all Characteristic Declarations within it using Read By Type Request (UUID = 0x2803). The response gives: the Properties byte (what operations are allowed), the Value Handle (the handle to use when reading/writing the value), and the Characteristic UUID (what type of data it is).
By UUID: Same Read By Type Request but the client looks for a specific characteristic UUID, getting only the matching characteristic declarations.
/* Discover all characteristics in a service: */
gatttool -b AA:BB:CC:DD:EE:FF \
--characteristics --start=0x000a --end=0x0019
/* Discover specific characteristic by UUID: */
gatttool -b AA:BB:CC:DD:EE:FF \
--characteristics --uuid=0x2A37
/* bluetoothctl shows characteristics automatically */
/* after list-attributes — type "Characteristic" entries */
Descriptors live between the Characteristic Value Declaration and the next Characteristic Declaration. The client uses Find Information Request to get handle-UUID pairs in the range between the characteristic’s value handle and the next characteristic’s handle. Common descriptors include the CCCD (0x2902 — enables notify/indicate), User Description (0x2901), and Presentation Format (0x2904).
/* Find descriptors for a characteristic: */
gatttool -b AA:BB:CC:DD:EE:FF \
--char-desc --start=0x000b --end=0x000d
/* Output typically shows: */
/* handle: 0x000b, uuid: 0x2803 (Characteristic Decl) */
/* handle: 0x000c, uuid: 0x2a37 (Heart Rate Measurement) */
/* handle: 0x000d, uuid: 0x2902 (Client Char Config = CCCD) */
Read Characteristic Value: ATT Read Request using the value handle. Returns up to ATT_MTU-1 bytes.
Read Using UUID: ATT Read By Type Request — lets the client read a characteristic value without needing its handle, only its UUID. Useful when the handle is not cached.
Read Long: ATT Read + Read Blob sequence for values larger than ATT_MTU-1 bytes. Client keeps issuing Read Blob with increasing offsets until the response is shorter than ATT_MTU-1.
Read Multiple: ATT Read Multiple Request — fetches several fixed-length characteristic values in one round trip.
/* Read a specific characteristic by handle: */
gatttool -b AA:BB:CC:DD:EE:FF --char-read --handle=0x000c
/* Read by UUID (no need to know handle): */
gatttool -b AA:BB:CC:DD:EE:FF --char-read --uuid=0x2A29
/* Returns Manufacturer Name String value */
/* BlueZ Python D-Bus read: */
char_iface.ReadValue({'offset': dbus.UInt16(0)})
Write Without Response: ATT Write Command — fire and forget, no acknowledgement. Fastest but unreliable.
Signed Write Without Response: ATT Signed Write Command — includes 12-byte CSRK signature for integrity without link encryption.
Write Characteristic Value: ATT Write Request — acknowledged write. Server confirms success or returns error.
Write Long: ATT Prepare Write + Execute Write sequence for values larger than ATT_MTU-3 bytes. Two-phase commit.
Reliable Writes: Multiple Prepare Write Requests where the client verifies each echoed response before sending the next chunk. If any echo doesn’t match, the client cancels with Execute Write (Flags=0x00) and retries.
/* Acknowledged write: */
gatttool -b AA:BB:CC:DD:EE:FF \
--char-write-req --handle=0x0025 --value=01
/* Write without response (faster, no ACK): */
gatttool -b AA:BB:CC:DD:EE:FF \
--char-write --handle=0x0025 --value=01
/* BlueZ D-Bus write with options: */
char_iface.WriteValue([0x01], {}) # Write Request
char_iface.WriteValue([0x01], {'type':'command'}) # Write Cmd
The client enables notifications by writing 0x0001 to the CCCD descriptor (UUID 0x2902) associated with the characteristic. The server then sends Handle Value Notification PDUs whenever the characteristic value changes. The client never sends a response — unreliable delivery but lowest overhead.
The client must re-enable notifications after every reconnection unless the bonded CCCD value is persistent on the server.
/* Enable notifications — write 0x0001 to CCCD handle: */
gatttool -b AA:BB:CC:DD:EE:FF --char-write-req \
--handle=0x000d --value=0100
/* Listen for incoming notifications: */
gatttool -b AA:BB:CC:DD:EE:FF --listen
/* bluetoothctl: */
[AA:BB:CC:DD:EE:FF]# select-attribute /org/.../char000b
[AA:BB:CC:DD:EE:FF]# notify on
Enable by writing 0x0002 to the CCCD. The server sends Handle Value Indication PDUs. The client must respond with Handle Value Confirmation before the server sends the next indication. Reliable delivery — server knows the client received each value. Higher latency than notification due to confirmation round-trip.
/* Enable indications — write 0x0002 to CCCD handle: */
gatttool -b AA:BB:CC:DD:EE:FF --char-write-req \
--handle=0x000d --value=0200
/* BlueZ automatically sends Confirmation for Indications */
/* The application just sees the value via D-Bus signal */
Reads the current value of a descriptor — for example, reading the CCCD to check whether notifications are currently enabled, or reading the User Description to get a human-readable string for a characteristic. Long descriptors use the Read Blob mechanism just like long characteristic values.
/* Read CCCD value (check if notifications enabled): */
gatttool -b AA:BB:CC:DD:EE:FF --char-read --handle=0x000d
/* Response 0100 = notifications enabled */
/* Response 0000 = disabled */
/* Read User Description descriptor: */
gatttool -b AA:BB:CC:DD:EE:FF --char-read --handle=0x000e
/* Returns UTF-8 string e.g. "Heart Rate" */
Writes to a descriptor using ATT Write Request. The most common use is writing to the CCCD (enabling/disabling notifications or indications). Long descriptors use Prepare Write + Execute Write. Most clients only write to CCCDs, but some application profiles use writable User Description or custom descriptors.
/* Disable notifications (write 0x0000 to CCCD): */
gatttool -b AA:BB:CC:DD:EE:FF --char-write-req \
--handle=0x000d --value=0000
/* Summary of CCCD values: */
/* 0x0000 = disable all */
/* 0x0001 = enable notifications */
/* 0x0002 = enable indications */
/* 0x0003 = enable both (if characteristic supports both) */
Table 13.2 — GATT Features and Procedures Summary
| # | Feature | Sub-procedures |
|---|---|---|
| 1 | Server Configuration | Exchange MTU |
| 2 | Primary Service Discovery | Discover All · By Service UUID |
| 3 | Relationship Discovery | Find Included Services |
| 4 | Characteristic Discovery | Discover All · By UUID |
| 5 | Characteristic Descriptor Discovery | Discover All Descriptors |
| 6 | Characteristic Value Read | Read · By UUID · Long · Multiple |
| 7 | Characteristic Value Write | Write Without Resp · Signed · Write · Long · Reliable |
| 8 | Characteristic Value Notification | Notifications |
| 9 | Characteristic Value Indication | Indications |
| 10 | Characteristic Descriptor Value Read | Read · Read Long |
| 11 | Characteristic Descriptor Value Write | Write · Write Long |
Chapter 13 Summary
GATT is the framework every BLE application rests on. It takes ATT’s flat attribute list and gives it a logical shape — services hold related characteristics, and profiles group services into complete device roles. The 11 GATT features provide a complete toolkit: discover what a device offers, read its current values, write new values, and subscribe to receive updates automatically.
Every BLE device exposes at least two mandatory services — Generic Access (device name, appearance) and Generic Attribute (Service Changed characteristic). Everything else is application-specific and defined by profile specifications that build on the foundation GATT provides.
/* Complete GATT client workflow in BlueZ shell: */
sudo bluetoothctl
[bluetooth]# scan on
[bluetooth]# connect AA:BB:CC:DD:EE:FF
[AA:BB:CC:DD:EE:FF]# menu gatt
/* Service/characteristic discovery (Feature 2, 4, 5): */
[AA:BB:CC:DD:EE:FF]# list-attributes
/* Navigate to a characteristic: */
[AA:BB:CC:DD:EE:FF]# select-attribute /org/.../char000b
/* Read (Feature 6): */
[AA:BB:CC:DD:EE:FF]# read
/* Write (Feature 7): */
[AA:BB:CC:DD:EE:FF]# write "0x01"
/* Enable notifications (Feature 8): */
[AA:BB:CC:DD:EE:FF]# notify on
/* Incoming notifications print automatically: */
/* [CHG] Attribute .../char000b Value: 0x48 bpm */
