29  Bluetooth GATT Review

29.1 Learning Objectives

  • Diagnose and fix the CCCD enablement requirement for BLE notifications using descriptor writes
  • Apply standard GATT services (Environmental Sensing, Heart Rate) instead of custom UUIDs for interoperability
  • Calculate supervision timeout values using the formula: timeout > (1 + latency) × interval × 2
  • Design BLE connection parameters within iOS constraints (15ms-2s interval, max 30 latency)
  • Implement bonding key persistence using non-volatile storage to survive device reboots

GATT (Generic Attribute Profile) is how BLE devices organize and share their data. This review covers common mistakes and misconceptions about GATT, like confusing services with characteristics or misunderstanding notification behavior. Avoiding these pitfalls saves hours of debugging in real BLE projects.

In 60 Seconds

The most common BLE implementation mistakes involve forgetting to write to the CCCD before expecting notifications, using custom UUIDs when standard GATT services exist, and miscalculating supervision timeout relative to connection interval and peripheral latency. Design for Apple iOS parameter constraints first to ensure cross-platform compatibility.

Key Concepts
  • GATT Caching: BLE 5.1 feature where a client stores the peer’s GATT service structure (with a database hash) to skip service discovery on reconnection, reducing connection setup latency
  • ATT Error Response: 9-byte PDU returned when an ATT operation fails; error codes include 0x01 (Invalid Handle), 0x02 (Read Not Permitted), 0x0F (Insufficient Encryption)
  • BLE Privacy: Feature using Resolvable Private Addresses (RPAs) that change periodically; prevents device tracking by passive scanners while allowing bonded devices to recognize each other
  • LE Secure Connections OOB: Pairing method where ECDH public keys are exchanged out-of-band (NFC, QR code) before BLE connection, providing the highest level of MITM protection
  • Characteristic Value Truncation: Silent data loss that occurs when a notification payload exceeds (ATT MTU - 3) bytes without negotiating larger MTU; no error is returned to the application
  • BLE Connection Supervision: Link Layer mechanism that drops a connection after supervision_timeout milliseconds without a valid packet, independent of application layer health checks
  • Pairing vs Bonding: Pairing establishes a temporary session key (STK/LTK) for the current connection; bonding additionally stores the LTK in non-volatile memory for future reconnections without re-pairing
  • BLE Whitelist (Filter Accept List): Controller-level filter that only responds to advertising or connection requests from listed device addresses, used to implement private networks
Minimum Viable Understanding

The most common BLE implementation mistakes involve forgetting to write to the CCCD before expecting notifications, using custom UUIDs when standard GATT services exist, and miscalculating supervision timeout relative to connection interval and peripheral latency. Designing for Apple iOS parameter constraints first ensures cross-platform compatibility.

Series Navigation

This is Part 3 of the Bluetooth Comprehensive Review series:

  1. Overview and Visualizations - Protocol stacks, state machines, initial scenarios
  2. Advanced Scenarios - Medical security, battery drain, smart locks
  3. GATT Pitfalls (this chapter) - Common implementation mistakes
  4. Assessment - Visual gallery and understanding checks

29.2 Common GATT Implementation Pitfalls

Pitfall: Not Enabling CCCD Before Expecting Notifications

The Mistake: Creating a BLE peripheral with NOTIFY characteristics, but notifications never arrive at the central device. The central connects, discovers services, but never receives pushed data even though the peripheral is calling notify().

Why It Happens: BLE notifications require the client to explicitly enable them by writing to the Client Characteristic Configuration Descriptor (CCCD). This is a security and power-saving feature - peripherals don’t spam data until the client opts in. Many developers forget this step, especially when transitioning from other protocols.

The Fix: After discovering characteristics, the central must write 0x0001 (notifications) or 0x0002 (indications) to the CCCD descriptor (UUID 0x2902):

// Central/Client side - MUST enable notifications
void on_characteristic_discovered(uint16_t char_handle, uint16_t cccd_handle) {
    // Write 0x0001 to CCCD to enable notifications
    uint8_t enable_notify[2] = {0x01, 0x00};  // Little-endian
    ble_gattc_write(conn_handle, cccd_handle, enable_notify, 2);
}

// Peripheral side - check if notifications are enabled
void send_sensor_reading(float value) {
    if (cccd_enabled) {  // Only notify if client subscribed
        ble_gatts_hvx(conn_handle, &hvx_params);
    }
    // Otherwise, client must poll via READ
}

Common symptoms: Works in nRF Connect (which auto-enables CCCD) but fails in custom apps. Debug by checking CCCD value after connection.

Pitfall: Using Custom UUIDs When Standard GATT Services Exist

The Mistake: Creating custom 128-bit UUIDs for common sensor data (temperature, heart rate, battery level) instead of using Bluetooth SIG standard services, breaking interoperability with existing apps and tools.

Why It Happens: Developers either don’t know standard services exist, or want “full control” over data format. Custom UUIDs require custom apps on every platform, while standard services work with generic BLE tools and OS integrations.

The Fix: Check the Bluetooth SIG GATT specifications before creating custom services. Use standard UUIDs with defined data formats:

// WRONG: Custom UUID for temperature
#define TEMP_SERVICE_UUID "12345678-1234-5678-1234-123456789abc"
// Requires custom app, no ecosystem support

// CORRECT: Standard Environmental Sensing Service
#define ENV_SENSING_SERVICE  0x181A  // Bluetooth SIG standard
#define TEMPERATURE_CHAR     0x2A6E  // Standard characteristic

// Standard data format for temperature (per GATT spec):
// sint16: temperature in 0.01 degrees Celsius
int16_t temp_value = (int16_t)(celsius * 100);
ble_gatts_notify(conn_handle, temp_char_handle, &temp_value, 2);

// Benefits of standard services:
// - Works with nRF Connect, LightBlue, and generic BLE apps
// - iOS/Android can display values in system Bluetooth settings
// - Interoperable with fitness apps, health platforms
// - Defined data encoding eliminates ambiguity

When to use custom UUIDs: Only for truly proprietary functionality that has no standard equivalent (e.g., device-specific configuration, firmware update protocol, vendor-specific commands).

Supervision timeout must accommodate worst-case response time with peripheral latency. The BLE specification requires:

\[ \begin{aligned} T_{\text{supervision}} &> (1 + \text{latency}) \times T_{\text{interval}} \times 2 \\[0.4em] \text{where:} \quad &T_{\text{interval}} \in [7.5\text{ ms}, 4\text{ s}] \\ &\text{latency} \in [0, 499] \\ &T_{\text{supervision}} \in [100\text{ ms}, 32\text{ s}] \end{aligned} \]

Example 1: Power-optimized sensor \[ \begin{aligned} T_{\text{interval}} &= 400\text{ ms},\quad \text{latency} = 4 \\[0.4em] T_{\text{min}} &= (1 + 4) \times 400\text{ ms} \times 2 = 4000\text{ ms} = 4\text{ s} \\[0.4em] T_{\text{recommended}} &= 4\text{ s} \times 1.5 \text{ (safety margin)} = 6\text{ s} \end{aligned} \]

Example 2: Common mistake (underestimated timeout) \[ \begin{aligned} T_{\text{interval}} &= 200\text{ ms},\quad \text{latency} = 10,\quad T_{\text{supervision}} = 2\text{ s} \\[0.4em] T_{\text{worst-case}} &= (1 + 10) \times 200\text{ ms} = 2.2\text{ s} > 2\text{ s} \quad \color{red}{\text{FAIL}} \\[0.4em] \text{Result:} \quad &\text{Random disconnections when peripheral uses full latency} \end{aligned} \]

Example 3: iOS-compatible parameters \[ \begin{aligned} T_{\text{interval}} &= 100\text{ ms} \text{ (iOS prefers ≤ 200 ms)},\quad \text{latency} = 4 \\[0.4em] T_{\text{min}} &= (1 + 4) \times 100\text{ ms} \times 2 = 1\text{ s} \\[0.4em] T_{\text{recommended}} &= \max(2\text{ s}, T_{\text{min}} \times 1.5) = 2\text{ s} \text{ (iOS minimum)} \end{aligned} \]

Key insight: The factor-of-2 multiplier accounts for connection event retries. Always verify that your timeout exceeds the worst-case response time by at least 50% to handle RF interference and packet loss.

Pitfall: Setting Supervision Timeout Too Short for High-Latency Peripherals

The Mistake: Using a short supervision timeout (e.g., 1 second) for a BLE connection to a peripheral that uses peripheral latency to skip connection events, resulting in unexpected disconnections during normal operation when no data is being exchanged.

Why It Happens: Developers set aggressive timeouts thinking “faster disconnect detection is better,” without accounting for the interaction between connection interval, peripheral latency, and supervision timeout. If peripheral latency allows skipping 10 events at 400ms intervals, the peripheral may not respond for 4 seconds—triggering a 1-second timeout.

The Fix: The supervision timeout must accommodate the worst-case response time based on your connection parameters. Use this formula:

Minimum supervision timeout = (1 + peripheral_latency) × connection_interval × 2

Example calculations:

Scenario A (fast response, no latency):
- Connection interval: 50ms
- Peripheral latency: 0 (respond every event)
- Minimum timeout: (1 + 0) × 50ms × 2 = 100ms
- Recommended: 500ms-1s (margin for retries)

Scenario B (power-optimized sensor):
- Connection interval: 400ms (maximum for iOS)
- Peripheral latency: 4 (skip up to 4 events when idle)
- Minimum timeout: (1 + 4) × 400ms × 2 = 4000ms
- Recommended: 6-10 seconds

Scenario C (ultra-low-power with max latency):
- Connection interval: 4s (BLE maximum)
- Peripheral latency: 0 (required at max interval)
- Minimum timeout: (1 + 0) × 4s × 2 = 8s
- Recommended: 16-32 seconds (BLE max is 32s)

Common mistake:
- CI: 200ms, Latency: 10, Timeout: 2s
- Worst-case response: (1+10) × 200ms = 2.2s > 2s timeout
- Result: Random disconnections when peripheral is idle!

Always verify: timeout > (1 + latency) × interval × safety_factor where safety_factor >= 2.

Pitfall: Ignoring iOS Connection Parameter Restrictions

The Mistake: Designing a BLE peripheral with connection parameters optimized for Android or embedded gateways (e.g., 500ms-4s connection intervals), then discovering that iPhones reject or override these parameters, causing connection failures or poor battery life on iOS.

Why It Happens: Apple enforces stricter connection parameter ranges than the BLE specification allows. Peripherals requesting parameters outside Apple’s limits will have their requests rejected or modified, leading to unexpected behavior that only appears during iOS testing.

The Fix: Design for Apple’s constraints first, then relax for Android/embedded if needed:

Apple's BLE Connection Parameter Requirements (as of iOS 17):

Connection Interval:
- Minimum: 15ms (BLE spec allows 7.5ms)
- Maximum: 2s (was 4s before iOS 11, then 2s)
- Must be multiple of 15ms

Peripheral Latency:
- Maximum: 30 (spec allows 499)
- Constraint: latency × interval ≤ 2 seconds

Supervision Timeout:
- Minimum: 2 seconds
- Maximum: 6 seconds
- Constraint: timeout > (1 + latency) × interval × 2

Recommended cross-platform parameters:

For responsive devices (wearables, input devices):
  min_interval: 15ms (12 × 1.25ms)
  max_interval: 30ms (24 × 1.25ms)
  latency: 0
  timeout: 2000ms

For power-optimized sensors:
  min_interval: 100ms (80 × 1.25ms)
  max_interval: 200ms (160 × 1.25ms)
  latency: 4
  timeout: 4000ms

For ultra-low-power (environmental sensors):
  min_interval: 400ms (320 × 1.25ms)
  max_interval: 500ms (400 × 1.25ms)
  latency: 3
  timeout: 6000ms

Common mistake: CI=4s (max BLE spec)
- Android: Works fine
- iOS: Silently reduced to 2s, doubling power consumption
- Embedded: Works fine

Always test on iOS devices early in development—parameter negotiation failures are silent!

29.3 Chapter Summary

Bluetooth has evolved from cable replacement to sophisticated IoT protocol. BLE revolutionized wireless sensors by enabling years of battery life.

Key Points:

  • Classic BT: Continuous connections (audio)
  • BLE: Intermittent, ultra-low power
  • Piconets: 7 active slaves max
  • Mesh: Scalable building automation
  • Profiles: Application-specific behavior
  • Security: Modern encryption & authentication

29.4 Original Source Figures (Alternative Views)

The following figures from the CP IoT System Design Guide provide alternative perspectives on Bluetooth concepts for review and comparison.

Complete Bluetooth protocol stack showing layered architecture from Radio layer through Baseband, Link Manager Protocol, L2CAP, RFCOMM, and Application Profiles including Serial Port Profile, Human Interface Device, Hands-Free Profile, and Advanced Audio Distribution Profile

Bluetooth protocol stack architecture

Source: CP IoT System Design Guide, Chapter 4 - Networking

BLE-specific protocol stack showing simplified architecture compared to Classic Bluetooth: Physical Layer, Link Layer, L2CAP, ATT (Attribute Protocol), GATT (Generic Attribute Profile), and GAP (Generic Access Profile) for connection management

BLE protocol stack layers

Source: CP IoT System Design Guide, Chapter 4 - Networking

Detailed comparison matrix of Bluetooth Classic, BLE, Zigbee, and Wi-Fi covering IEEE standards, frequency bands, data rates, range, power consumption, network size, and optimal use cases for informed protocol selection in IoT projects

Wireless technology comparison for IoT

Source: CP IoT System Design Guide, Chapter 4 - Networking

Bluetooth Classic packet format showing 72-bit access code for synchronization and piconet identification, 54-bit header with AM_ADDR, packet type, flow control and error checking fields, and variable-length payload section

Bluetooth packet structure details

Source: CP IoT System Design Guide, Chapter 4 - Networking

BLE data frame showing 1-byte preamble, 4-byte access address, variable PDU with header and payload, and 3-byte CRC for reliable data transmission in low-power applications

BLE data frame structure

Source: CP IoT System Design Guide, Chapter 4 - Networking

Summary of Bluetooth operational modes comparing Active, Sniff, Hold, and Park states in terms of power consumption, response latency, and appropriate use cases for battery optimization in connected devices

Bluetooth power-saving modes

Source: CP IoT System Design Guide, Chapter 4 - Networking

29.6 Summary

  • CCCD subscription is mandatory for BLE notifications – clients must write 0x0001 to the Client Characteristic Configuration Descriptor (UUID 0x2902) before the peripheral will push data
  • Use standard GATT services (e.g., Environmental Sensing 0x181A, Heart Rate 0x180D) whenever possible to ensure interoperability with generic BLE apps and OS integrations
  • Calculate supervision timeout carefully using the formula: timeout > (1 + peripheral_latency) x connection_interval x 2 to avoid random disconnections during idle periods
  • Design for iOS constraints first – Apple enforces stricter connection parameter limits (max 2s interval, max 30 latency) that silently override requests outside the allowed range
  • Store bonding keys in non-volatile storage (NVS/flash) to persist across reboots and avoid requiring re-pairing after device restarts
  • Enforce security at the device level, not just in the app – other BLE clients can bypass app-only controls, so GATT characteristic permissions and device-side timeouts are essential for regulated devices

Common Pitfalls

Setting all GATT characteristic permissions to “Read/Write No Security” exposes sensitive data (device configuration, health metrics, access credentials) to any nearby BLE device without authentication. Implement minimum required permissions: read-only data → Read without security; configuration data → Authenticated Write; keys/credentials → Encrypted Authenticated Read/Write.

BLE GATT clients that assume all operations succeed and do not parse ATT Error Responses will silently fail when a server returns errors. Common causes: insufficient encryption (write to characteristic requiring authenticated pairing), attribute handle changed after firmware update (handle stale from cache), or write value out of characteristic value range. Always implement ATT error handler and log the opcode, handle, and error code.

BLE pairing produces both a Short Term Key (STK, used only for the current pairing session) and, during bonding, a Long Term Key (LTK, stored for future sessions). Using the STK beyond its intended scope or misunderstanding that the LTK must be securely stored in NVS is a common implementation error. The STK should be discarded after the pairing session; only the LTK (and IRK, CSRK) should be persisted.

BLE GATT Write (without response) is fire-and-forget with no acknowledgment; packet loss is silent. GATT Write (with response) provides ATT acknowledgment but no application-level confirmation of processing. For reliable command delivery, implement an application-level acknowledgment: send a command via Write With Response, wait for a confirmation characteristic notification within 500 ms, and retry up to 3 times on timeout.

29.7 What’s Next

Chapter Why It Matters
Bluetooth Review: Assessment Test your understanding with a visual gallery and comprehensive knowledge checks covering all four review parts
Bluetooth Review: Overview Revisit protocol stacks, GAP state machines, and initial deployment scenarios to consolidate the full picture
Bluetooth Review: Advanced Scenarios Apply the pitfall knowledge from this chapter to medical security, battery drain, and smart lock case studies
Bluetooth Fundamentals and Architecture Analyze GATT profile design in depth and distinguish correct connection parameter negotiation from common mistakes
Bluetooth Security Evaluate bonding, pairing modes, key storage strategies, and device-side enforcement for regulated BLE deployments
Bluetooth Applications Construct real-world BLE deployment patterns that avoid the pitfalls covered in this chapter