8 BLE Protocol Stack and GATT
Generic Attribute Profile and Service Architecture
ble, gatt, bluetooth, services, characteristics, profiles, att, gap
- ATT (Attribute Protocol): BLE’s foundational client-server protocol where all data is represented as attributes with 16-bit handles, types, and permissions
- GATT (Generic Attribute Profile): Builds on ATT to define services (groups of related characteristics) and characteristics (typed values with descriptors)
- Service UUID: 16-bit (assigned by Bluetooth SIG) or 128-bit (custom) identifier for a GATT service; e.g., 0x180D = Heart Rate Service
- Characteristic: A typed data value within a service, with properties (Read, Write, Notify, Indicate) and optional descriptors (e.g., CCCD)
- CCCD (Client Characteristic Configuration Descriptor): 2-byte descriptor at handle N+1 that a central writes to 0x0001 to enable notifications or 0x0002 for indications
- L2CAP (Logical Link Control and Adaptation Protocol): Multiplexing layer below ATT/GATT that segments large packets and manages multiple simultaneous channels
- HCI (Host Controller Interface): Standardized interface between the Bluetooth host stack (running on application processor) and the controller (radio hardware)
- LE Data Length Extension (DLE): BLE 4.2+ feature allowing PDU payload up to 251 bytes (vs default 27), reducing per-packet overhead for bulk transfers
BLE uses the Generic Attribute Profile (GATT) to organize data into a hierarchy of Services and Characteristics, enabling structured, efficient data exchange between devices. The GATT client-server model, combined with standard Bluetooth profiles and beacon protocols like iBeacon and Eddystone, forms the foundation for virtually all BLE IoT applications.
8.1 Learning Objectives
By the end of this chapter, you will be able to:
- Describe the BLE protocol stack layers and their functions
- Explain the GATT client-server model for data exchange
- Create and interact with BLE services and characteristics
- Compare standard Bluetooth profiles for IoT applications and select the appropriate profile for a given use case
- Design GATT architectures for sensor data communication
8.2 Introduction
Bluetooth Low Energy uses a well-defined protocol stack that enables efficient data exchange between devices. At the heart of BLE data communication is the Generic Attribute Profile (GATT), which defines how devices expose and consume structured data through services and characteristics.
This chapter explores the BLE protocol architecture, GATT fundamentals, and the standard profiles that enable interoperable IoT applications.
Think of GATT like a restaurant menu: - Service = Menu category (Drinks, Appetizers, Main Course) - Characteristic = Individual item (Coffee, Salad, Steak) - Properties = What you can do (Read the price, Order it, Get refills)
A heart rate monitor has a “Heart Rate Service” containing a “Heart Rate Measurement” characteristic that you can read or subscribe to for updates.
“GATT is like my own personal menu!” Sammy the Sensor explained. “I organize all my data into neat categories called Services. My Temperature Service has a Temperature Characteristic, and my Humidity Service has a Humidity Characteristic. When a phone wants to read my data, it just browses my menu!”
“It is like a restaurant,” Lila the LED added. “Each Service is a section of the menu, like Appetizers or Desserts. Each Characteristic is an individual dish you can order. And the Properties tell you what you can do – some you can just read, others you can write to, and some will send you updates automatically, like a waiter bringing refills!”
Max the Microcontroller nodded. “I am the one who runs the GATT server. When a phone connects, I hand it the menu and wait for requests. The phone can read a sensor value, write a command, or subscribe to notifications so I push updates automatically. The whole protocol stack – from the radio at the bottom to the app at the top – works together to make this happen smoothly.”
“And the best part,” Bella the Battery said, “is that GATT is super efficient. Instead of sending huge packets of data, it sends tiny, focused messages. That means less radio time, which means I last much longer!”
8.3 BLE Protocol Stack
The BLE protocol stack is organized into layers, each with specific responsibilities:
When a BLE client (smartphone) reads a temperature value from a sensor’s GATT server:
- Application Request: App calls
readCharacteristic(0x2A6E)for Temperature UUID - GATT Layer: Converts UUID to ATT handle (e.g., handle 0x000E) and creates ATT Read Request
- ATT Layer: Builds Read Request PDU with opcode 0x0A + 2-byte handle → 3-byte packet
- L2CAP Layer: Wraps ATT packet in L2CAP frame with 4-byte header (length + channel ID)
- Link Layer: Adds Link Layer header, encrypts payload if bonded, adds CRC → sends as BLE data packet
- PHY Layer: Modulates packet using GFSK at 1 Mbps, transmits on current data channel (0-36)
- Server Receives: Reverse process – PHY demodulates, Link Layer verifies CRC, decrypts
- Server GATT: Looks up handle 0x000E → finds Temperature characteristic → reads sensor value
- ATT Response: Builds Read Response PDU with opcode 0x0B + 2-byte value (e.g., 0x09F6 = 25.50°C) — ATT Read Response does not repeat the handle
- Client Receives: Parses response, converts 0x09F6 to float → app displays “25.5°C”
Entire roundtrip: 7-15 milliseconds (1-2 ms radio time + encryption overhead + processing). No pairing needed if characteristic is readable without authentication.
8.3.1 Stack Architecture
8.3.2 Layer Functions
| Layer | Function |
|---|---|
| PHY | 2.4 GHz radio, modulation, 1M/2M/Coded PHY |
| Link Layer | Advertising, scanning, connection management |
| L2CAP | Multiplexing, segmentation, flow control |
| ATT | Attribute Protocol - read/write attribute values |
| GATT | Service/characteristic organization of attributes |
| GAP | Device roles, advertising, connection procedures |
| SMP | Pairing, bonding, encryption key management |
8.4 Generic Attribute Profile (GATT)
GATT defines how BLE devices exchange data using a client-server model:
8.4.1 GATT Roles
| Role | Description | Typical Device |
|---|---|---|
| GATT Server | Exposes services and characteristics | Sensors, peripherals |
| GATT Client | Reads/writes data from servers | Smartphones, gateways |
GATT roles are independent of GAP roles: - A BLE Central (GAP) can be a GATT Server - A BLE Peripheral (GAP) can be a GATT Client - Most often: Peripheral = Server, Central = Client
8.4.2 GATT Hierarchy
GATT organizes data in a three-level hierarchy:
8.4.3 Services
A service is a collection of related characteristics:
| Component | Description | Example |
|---|---|---|
| UUID | 16-bit (standard) or 128-bit (custom) identifier | 0x180D (Heart Rate) |
| Primary/Secondary | Primary = standalone, Secondary = included by other | Usually Primary |
| Characteristics | Data values within the service | HR Measurement, Body Location |
Standard Service UUIDs:
| UUID | Service Name | Use Case |
|---|---|---|
| 0x180D | Heart Rate Service | Fitness devices |
| 0x180F | Battery Service | Battery level reporting |
| 0x181A | Environmental Sensing | Temperature, humidity |
| 0x1800 | Generic Access | Device name, appearance |
| 0x1801 | Generic Attribute | Service changed indication |
8.4.4 Characteristics
A characteristic contains the actual data value with metadata:
8.4.5 Characteristic Properties
Properties define how clients can interact with a characteristic:
| Property | Bit | Description |
|---|---|---|
| Broadcast | 0x01 | Include in advertising data |
| Read | 0x02 | Client can read value |
| Write Without Response | 0x04 | Fast write, no confirmation |
| Write | 0x08 | Write with confirmation |
| Notify | 0x10 | Server pushes updates (no ACK) |
| Indicate | 0x20 | Server pushes updates (with ACK) |
| Authenticated Signed Writes | 0x40 | Signed writes allowed |
| Extended Properties | 0x80 | Additional properties in descriptor |
8.4.6 Client Characteristic Configuration Descriptor (CCCD)
The CCCD (UUID 0x2902) controls notifications and indications:
CCCD Value:
0x0000 = Notifications/Indications disabled
0x0001 = Notifications enabled
0x0002 = Indications enabled
To receive real-time updates from a characteristic:
- Connect to the GATT server
- Discover services and characteristics
- Write 0x0001 to the CCCD to enable notifications
- Handle incoming notification callbacks
Without writing to the CCCD, you will NOT receive updates even if the characteristic has the Notify property!
8.5 Standard Bluetooth Profiles
Bluetooth SIG defines standard profiles for interoperability:
8.5.1 Common IoT Profiles
| Profile | Services | Use Case |
|---|---|---|
| Heart Rate Profile | Heart Rate (0x180D) | Fitness trackers, monitors |
| Health Thermometer | Health Thermometer (0x1809) | Medical thermometers |
| Blood Pressure | Blood Pressure (0x1810) | BP monitors |
| Environmental Sensing | Environmental (0x181A) | Temperature, humidity sensors |
| Proximity | Link Loss, TX Power | Proximity beacons |
| HID over GATT (HOGP) | HID Service | BLE keyboards, mice |
8.5.2 Classic Bluetooth Profiles
| Profile | Abbreviation | Use Case |
|---|---|---|
| Advanced Audio Distribution | A2DP | Stereo audio streaming |
| Audio/Video Remote Control | AVRCP | Media controls |
| Hands-Free Profile | HFP | Car kits, headsets |
| Human Interface Device | HID | Keyboards, mice |
| Serial Port Profile | SPP | UART replacement |
| Object Push Profile | OPP | File transfer |
8.6 BLE Advertising
BLE advertising enables device discovery and connectionless data broadcast:
8.6.1 Advertising Packet Structure
8.6.2 Advertising Types
| Type | Code | Description | Connectable |
|---|---|---|---|
| ADV_IND | 0x00 | Connectable undirected | Yes |
| ADV_DIRECT_IND | 0x01 | Connectable directed | Yes |
| ADV_SCAN_IND | 0x02 | Scannable undirected | No |
| ADV_NONCONN_IND | 0x03 | Non-connectable | No |
8.6.3 Advertising Channels
BLE uses 3 dedicated advertising channels:
| Channel | Frequency | Wi-Fi Overlap |
|---|---|---|
| 37 | 2402 MHz | Avoids Ch 1 |
| 38 | 2426 MHz | Avoids Ch 6 |
| 39 | 2480 MHz | Avoids Ch 11 |
8.7 iBeacon and Eddystone
BLE beacons broadcast location/proximity information:
8.7.1 iBeacon (Apple)
| Field | Size | Description | Example |
|---|---|---|---|
| UUID | 16 bytes | Application/company identifier | Store chain ID |
| Major | 2 bytes | Coarse location | Store number |
| Minor | 2 bytes | Fine location | Aisle/department |
| TX Power | 1 byte | Calibrated RSSI at 1m | Distance estimation |
8.7.2 Eddystone (Google)
Eddystone supports multiple frame types:
| Frame | Description | Use Case |
|---|---|---|
| Eddystone-UID | Unique ID (similar to iBeacon) | Asset tracking |
| Eddystone-URL | Broadcasts URL | Physical web |
| Eddystone-TLM | Telemetry (battery, temp) | Beacon health |
| Eddystone-EID | Ephemeral ID (rotating) | Secure beacons |
8.8 Inline Knowledge Check
8.8.1 Knowledge Check: GATT Characteristics
8.8.2 Knowledge Check: BLE Advertising Channels
8.8.3 Knowledge Check: BLE Beacon Protocols
8.9 Real-World BLE Deployments: Scale and Design Lessons
8.9.1 Estimote Beacon Network at Macy’s (Retail Proximity Marketing)
In 2015, Macy’s deployed over 4,000 Estimote BLE beacons across its flagship Herald Square store and 800+ locations nationwide. The deployment used iBeacon-format advertisements on all three advertising channels (37, 38, 39) with a 350 ms advertising interval, balancing discovery speed against battery life.
Key technical parameters:
| Parameter | Value | Rationale |
|---|---|---|
| Advertising interval | 350 ms | Fastest reliable detection within 2 seconds |
| TX power | -12 dBm | 5-8 m range per beacon (aisle-level accuracy) |
| Battery | CR2477 (1,000 mAh) | 2.5 years at 350 ms interval |
| UUID/Major/Minor | Chain-wide UUID, store-specific Major, aisle-level Minor | Hierarchical location for 800+ stores |
| Beacon density | 1 per 25 m2 | Triangulation accuracy within 2.5 m |
Outcome: App engagement increased 16% in beacon-enabled stores. The deployment revealed that advertising interval tuning is critical – at 100 ms, battery life dropped below 1 year; at 1 second, customers walked past before detection.
8.9.2 Dexcom G6 Continuous Glucose Monitor (Medical GATT Design)
The Dexcom G6 CGM uses BLE GATT to stream glucose readings from a body-worn sensor to a smartphone. The GATT server exposes a custom Glucose Service (UUID 0x1808) with the following characteristic design:
| Characteristic | UUID | Properties | Update Rate |
|---|---|---|---|
| Glucose Measurement | 0x2A18 | Notify | Every 5 min |
| Glucose Feature | 0x2A51 | Read | On discovery |
| Record Access Control | 0x2A52 | Write, Indicate | On demand |
Design decisions with real impact:
- Notify (not Indicate): G6 uses Notify for glucose readings because Indicate’s ACK mechanism adds ~7 ms latency per reading, and with 288 readings per day, the cumulative radio time drains the sensor’s 10-day battery. Missing one reading is acceptable since the next arrives in 5 minutes.
- Connection interval: 30 ms (faster readings during urgent alerts) to 500 ms (power saving during normal operation), negotiated via L2CAP Connection Parameter Update.
- Bonding required: SMP Level 3 (authenticated MITM protection) mandatory per FDA guidance on wireless medical devices. Without bonding, a nearby attacker could spoof glucose readings, leading to dangerous insulin dosing.
Scale: Over 2 million active G6 users worldwide (2023), making it one of the largest GATT-based medical device deployments. The BLE protocol stack handles 99.7% connection reliability within the 6 m required range.
8.9.3 BLE GATT Design Decision Framework
When designing a BLE sensor product, these choices determine battery life, reliability, and user experience:
| Design Decision | Low-Power Choice | High-Reliability Choice | Typical Trade-off |
|---|---|---|---|
| Notification vs Indication | Notify (no ACK) | Indicate (ACK required) | 30% battery savings vs guaranteed delivery |
| Connection interval | 500 ms - 4 s | 7.5 - 30 ms | 10x battery savings vs sub-second latency |
| PHY selection | Coded PHY (500 Kbps) | 2M PHY | 4x range vs 2x throughput |
| Advertising interval | 1-2 s | 100-200 ms | 5x battery vs 10x faster discovery |
| Service design | Single service, few characteristics | Multiple services, rich metadata | Smaller ATT table vs better discoverability |
8.9.4 Why GATT Uses a Fixed ATT MTU of 23 Bytes by Default
One of the most misunderstood aspects of BLE is why the default Attribute Protocol Maximum Transmission Unit (ATT MTU) is only 23 bytes – a value that seems absurdly small for modern wireless communication.
The math traces back to BLE 4.0’s Link Layer design. A BLE data packet has a maximum payload of 27 bytes at the Link Layer. After subtracting L2CAP header overhead (4 bytes), exactly 23 bytes remain for the ATT layer. Within those 23 bytes, the ATT protocol uses 3 bytes for its own header (1-byte opcode + 2-byte attribute handle), leaving only 20 bytes of usable data per notification or read response.
The 23-byte MTU limit directly impacts throughput. Consider transferring a 200-byte firmware update block:
\[\text{Packets Required} = \lceil \frac{\text{Payload Size}}{\text{MTU} - \text{Header}} \rceil = \lceil \frac{200}{20} \rceil = 10 \text{ packets}\]
At 100ms connection interval with 2ms packet time, total transfer time: \(10 \times 100\text{ms} = 1\) second.
With MTU negotiated to 247 bytes (244 usable): Only 1 packet needed, transfer in 100ms—10× faster.
The practical impact is significant for IoT sensor design. Consider a BLE environmental sensor that needs to transmit: temperature (2 bytes), humidity (2 bytes), pressure (4 bytes), air quality index (2 bytes), CO2 concentration (2 bytes), and a 4-byte timestamp. That is 16 bytes – just barely fits in a single 20-byte notification. If the sensor also needed to include PM2.5 particulate data (2 bytes) and battery voltage (2 bytes), the 20 bytes are exceeded, requiring either two notifications (doubling radio time and power) or MTU negotiation.
BLE 4.2 introduced Data Length Extension (DLE) which allows the Link Layer payload to grow to 251 bytes, and MTU negotiation can increase the ATT MTU up to 512 bytes. However, both sides must support it, and many deployed BLE peripherals and older smartphones still default to 23 bytes. A robust BLE product must be designed to work at the 23-byte minimum while optionally negotiating larger MTUs when available. The Dexcom G6 CGM, for example, segments its glucose readings into 20-byte notification payloads precisely because it must support the widest range of smartphones.
8.10 Summary
This chapter covered the BLE protocol stack and GATT:
- Protocol Stack: Layered architecture from PHY through GATT to Application
- GATT Model: Client-server architecture for data exchange
- Hierarchy: Profile → Services → Characteristics → Values/Descriptors
- Properties: READ, WRITE, NOTIFY, INDICATE control data access
- CCCD: Must write 0x0001 to enable notifications
- Standard Profiles: Heart Rate, Environmental Sensing, HID, and more
- Beacons: iBeacon (Apple) and Eddystone (Google) for location services
| Core Concept | Builds On | Enables | Common Confusion |
|---|---|---|---|
| GATT Services | ATT (Attribute Protocol) | Structured sensor data exchange | Service UUID vs Characteristic UUID |
| CCCD (0x2902) | Characteristic Descriptors | Notifications/Indications opt-in | Assuming Notify property auto-enables |
| ATT MTU (23 bytes default) | L2CAP payload size | Determines max notification size | Forgetting 3-byte ATT header overhead |
| iBeacon UUID/Major/Minor | BLE advertising packets | Hierarchical location identification | UUID is app ID, not location |
| NOTIFY vs INDICATE | Characteristic properties | Latency vs reliability tradeoff | Indicate adds ~7ms ACK overhead |
Scenario: Design a custom GATT service for a battery-powered environmental monitor that measures temperature, humidity, air pressure, and CO2 concentration. The device reports to a smartphone app every 30 seconds.
Design requirements:
- Temperature: -40°C to +85°C, 0.01°C resolution
- Humidity: 0-100%, 0.01% resolution
- Pressure: 300-1100 hPa, 0.1 hPa resolution
- CO2: 400-10,000 ppm, 1 ppm resolution
- Battery level: 0-100%
- Sampling interval: configurable (1-300 seconds)
GATT service structure (Environmental Sensing Service, UUID 0x181A):
| Characteristic | UUID | Properties | Format | Example |
|---|---|---|---|---|
| Temperature | 0x2A6E (standard) | READ, NOTIFY | int16, 0.01 C | 25.50 C = 0x09F6 |
| Humidity | 0x2A6F (standard) | READ, NOTIFY | uint16, 0.01% | 65.25% = 0x1985 |
| Pressure | 0x2A6D (standard) | READ, NOTIFY | uint32, 0.1 Pa | 1013.2 hPa = 0x0018B5A8 |
| CO2 (custom) | 128-bit generated | READ, NOTIFY | uint16, 1 ppm | 850 ppm = 0x0352 |
| Sampling Interval (custom) | 128-bit generated | READ, WRITE | uint16, seconds | Range 1-300, default 30 |
| Battery Level | 0x2A19 (standard) | READ, NOTIFY | uint8, percentage | 75% = 0x4B |
Why these design choices:
| Decision | Rationale |
|---|---|
| Use standard UUIDs where available | Ensures interoperability with generic BLE apps (nRF Connect, LightBlue) |
| Use NOTIFY (not INDICATE) | Sensor data updates are frequent; occasional packet loss acceptable. Saves 7ms per notification. |
| int16 for temperature | Supports negative values. Bluetooth SIG standard format. |
| uint16 for humidity/CO2 | No negative values. 16 bits sufficient for range. |
| 0.01 resolution for temp/humidity | Matches typical sensor accuracy (DHT22, BME280). Higher resolution wastes bandwidth. |
| Separate characteristics | Allows app to subscribe only to needed sensors (humidity-only monitoring saves battery). |
| Configurable interval via WRITE | User can trade-off responsiveness vs battery life without firmware reflash. |
Data payload calculation:
Per notification (all sensors): - Temperature: 2 bytes - Humidity: 2 bytes - Pressure: 4 bytes - CO2: 2 bytes - Battery: 1 byte - Total: 11 bytes (fits in single 20-byte ATT notification)
At 30-second intervals, daily notifications: 86,400 ÷ 30 = 2,880 notifications
Daily data: 2,880 × 11 bytes = 31,680 bytes ≈ 31 KB/day
Power budget:
| Activity | Current | Time per interval | Energy per interval |
|---|---|---|---|
| Sleep | 5 µA | 29.5 sec | 147.5 µAs |
| Sensor read | 2 mA | 200 ms | 400 µAs |
| BLE TX (notify) | 8 mA | 300 ms | 2,400 µAs |
| Total per interval | 30 sec | 2,947.5 µAs |
Daily energy: 2,947.5 µAs/interval × 2,880 intervals = 8,488,800 µAs = 2.36 mAh
Battery life (CR2032, 225 mAh): 225 ÷ 2.36 = 95 days ≈ 3 months
| Criterion | Use NOTIFY | Use INDICATE | Winner |
|---|---|---|---|
| Latency | 0.5-2 ms (fire-and-forget) | 7-15 ms (wait for ACK) | Notify for real-time |
| Throughput | 100% (no ACK blocking) | 60-80% (ACK overhead) | Notify for streaming |
| Reliability | Best-effort (packet may be lost) | Guaranteed (retried until ACK) | Indicate for critical |
| Battery (peripheral) | Lower (single TX per update) | Higher (TX + RX ACK) | Notify for battery life |
| Use case examples | Heart rate, temperature, GPS | Battery low alarm, config change ACK | Context-dependent |
Real-world examples:
| Device | Data Type | Method | Why |
|---|---|---|---|
| Fitness tracker | Heart rate (1/sec) | NOTIFY | 60 readings/min. Occasional loss acceptable (next reading is 1s away). |
| Glucose monitor | Blood sugar (1/5min) | NOTIFY | 12 readings/hour. Missing one reading not critical (next is 5 min away). Medical devices still use Notify because throughput matters more than guaranteed delivery of every single reading. |
| Smart lock | Low battery warning | INDICATE | Critical alert. User must know battery is dying to prevent lockout. |
| Firmware update | Block transfer status | INDICATE | Each block confirmation required before sending next. Data integrity critical. |
| Industrial sensor | Vibration spike alert | INDICATE | Alarm condition. Missing this could result in equipment failure. Must guarantee delivery. |
Decision rule:
if (data_rate > 1 Hz AND next_reading_replaces_old):
use NOTIFY
elif (data_is_alarm OR configuration_change):
use INDICATE
elif (battery_critical OR low_latency_required):
use NOTIFY
else:
use INDICATE # default to reliability
The error: A developer creates a BLE server with a characteristic that has the NOTIFY property, connects from a client app, but no notifications arrive. They debug for hours checking the server code, only to discover they never enabled notifications in the client.
What actually happens:
Server side (ESP32, simplified):
BLECharacteristic *pTempChar = pService->createCharacteristic(
"0x2A6E",
BLECharacteristic::PROPERTY_NOTIFY
);
pTempChar->addDescriptor(new BLE2902()); // Add CCCD
// Later, in loop():
float temp = readTemperature();
pTempChar->setValue(temp);
pTempChar->notify(); // Developer expects this to push to clientClient side (what the developer forgot):
# WRONG - missing CCCD write
async with BleakClient(address) as client:
await client.start_notify(temp_char_uuid, notification_handler)
# start_notify() DOES write to CCCD automatically
# But manual connection code often forgets:
# What developers manually connecting often forget:
cccd_handle = 0x000F # CCCD descriptor handle
await client.write_gatt_char(cccd_handle, b'\x01\x00') # Enable notify
# ^^ THIS STEP IS REQUIREDWhy this is confusing:
The Bluetooth specification says characteristics with PROPERTY_NOTIFY “support” notifications, but the client must explicitly opt-in by writing to the CCCD (Client Characteristic Configuration Descriptor, UUID 0x2902).
CCCD values:
0x0000= Notifications and Indications disabled (default after connection)0x0001= Notifications enabled0x0002= Indications enabled
Debugging checklist:
- Use a BLE sniffer (nRF Sniffer, Wireshark with BTLE plugin) to verify:
- Client writes
0x0001to CCCD handle (should happen immediately after service discovery) - Server sends ATT Handle Value Notification PDUs (opcode 0x1B)
- Client writes
- In nRF Connect app, tap the characteristic → tap “Enable notifications” → you’ll see the CCCD write in the log
- Check server logs for
notify()calls – they should succeed even if no client is listening (server doesn’t know if CCCD is enabled)
The fix:
Most BLE libraries handle CCCD automatically when you call start_notify() or enableNotifications(). But if you’re doing manual ATT operations (low-level libraries, custom stack), you MUST write to the CCCD before expecting notifications.
Production impact: A medical device manufacturer shipped 5,000 glucose monitors with a companion app that forgot the CCCD write step for iOS (it worked on Android because their Android library auto-enabled CCCD). iOS users saw no glucose readings. A firmware update couldn’t fix it (server side was correct); they had to force-update the iOS app.
8.11 See Also
- Bluetooth Fundamentals - BLE architecture and low-power design
- Connection Establishment - Connection parameters and intervals
- Bluetooth Implementation - ESP32 GATT server/client code
- Bluetooth Applications - Real-world BLE use cases
- Bluetooth Security - Pairing, bonding, and encryption
Common Pitfalls
CCCD values are lost when a BLE connection drops unless the peripheral stores them against the bonded device’s identity key. A server that forgets CCCD settings requires the client to re-subscribe after every reconnection, causing missed notifications. Use bonding (long-term key exchange) and store CCCD state in non-volatile memory indexed by the peer IRK.
GATT Notify sends data without acknowledgment; Indicate requires an ATT acknowledgment PDU before the server can send the next indication. Using Notify for critical data (e.g., alarms) risks silent data loss if the central’s buffers are full. Use Indicate for reliability-critical events and Notify for high-rate streaming where occasional drops are acceptable.
BLE specifications prohibit hardcoding GATT attribute handles because they can change with firmware updates. Always use GATT service/characteristic discovery (ATT Find By Type Value, Read By Group Type) at connection time, or cache handles with the peer’s database hash (Bluetooth 5.1+ GATT Caching feature) to avoid re-discovery.
The default ATT MTU is 23 bytes (21 bytes payload after ATT header). Transferring a 512-byte characteristic value without MTU negotiation requires 25 ATT Read Blob operations. Send an ATT Exchange MTU Request immediately after connection to negotiate up to 517 bytes, reducing a 512-byte transfer from 25 packets to 2.
8.12 What’s Next
| Topic | Chapter | Why Read It |
|---|---|---|
| BLE Implementation | Bluetooth Implementation and Labs | Hands-on ESP32 GATT server/client code, Wokwi simulations, and exercises |
| Connection Establishment | Connection Establishment | Connection parameters, intervals, and the state machine from advertising to connected |
| Bluetooth Fundamentals | Bluetooth Fundamentals | Core BLE architecture, radio, and low-power design principles |
| Bluetooth Applications | Bluetooth Applications | Real-world use cases: fitness trackers, medical devices, smart home |
| Bluetooth Security | Bluetooth Security | SMP pairing, bonding, LE Secure Connections, and attack mitigations |
| Wireless Networking Core | Wireless Network Overview | How BLE fits within the broader landscape of IoT wireless protocols |