📶 BLE Basics and Designing Your Gadget's GATT Service

Learning Objectives

By the end of this lesson you can:

  • Explain how a phone and the ESP32 find each other and start a conversation.
  • Describe the GATT hierarchy (service, characteristic, value) in plain words.
  • Choose the right properties for a characteristic: Read, Write, Notify, Indicate.
  • Write down your own gadget's service layout with one data characteristic and one control characteristic.
  • Explain why custom services use 128-bit UUIDs.

Quick Review

In the last module you learned that Serial is a two-way contract: Serial.begin(115200) on the board and the same speed on the monitor, or nothing makes sense. BLE works on the same idea at a higher level. Two devices must agree on the shape of the data before the data is useful. Keep that word, contract, in mind for the next 20 minutes.

Theory

Two roles: who asks, who answers

Every BLE connection has two sides:

  • The central scans for devices and starts the connection. In your project this is the phone.
  • The peripheral advertises itself and waits to be connected. In your project this is the ESP32.

The ESP32 is called a server here because it holds the data. The phone is the client because it asks for that data. A device can play both roles, but your gadget only needs the peripheral side.

Advertising and connecting are two different phases

Before any data moves, the ESP32 advertises: it broadcasts small packets with its name and optionally a list of service UUIDs. That is how your phone can show the gadget in a scan list while no connection exists.

Once the phone connects, advertising stops and the two devices are in a connection. Now the phone can explore the ESP32's data structure. This exploration is where GATT comes in.

The GATT hierarchy

GATT (Generic Attribute Profile) is the agreed format for how data is organised after connection. Think of it as a small filing cabinet:

flowchart TD
  Dev["🔌 ESP32 peripheral"] --> Svc["📦 Service: your gadget"]
  Svc --> Dat["📈 Data characteristic<br/>Notify"]
  Svc --> Ctl["🎛️ Control characteristic<br/>Write"]
  Dat --> ValD["Value: sensor reading"]
  Ctl --> ValC["Value: command bytes"]
  • A service is a container that groups related characteristics under one UUID.
  • A characteristic is one single data value, plus the rules for how it may be used.
  • Each characteristic has a value (the bytes themselves) and optional descriptors (metadata).

Your gadget gets exactly one custom service. That keeps the phone side simple.

Properties: the rules attached to a characteristic

Properties tell both sides what operations are legal. The four you will meet most:

Property Direction Meaning
Read phone pulls Phone asks once, ESP32 answers with the current value.
Write phone pushes Phone sends bytes, ESP32 receives them.
Notify ESP32 pushes ESP32 sends a value whenever it wants, no acknowledgement.
Indicate ESP32 pushes Same as Notify, but the phone must acknowledge each message.

Notifications are the workhorse for sensor streams because they are cheap and fast. Indications are safer but slower. For a stream of readings you want Notify, not Indicate.

UUIDs: how the phone tells things apart

Every service and every characteristic is labelled with a UUID. The Bluetooth SIG publishes standard 16-bit UUIDs (for example the battery service). Your own creations cannot use those, so custom services use full 128-bit UUIDs in the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.

Two rules that save hours of debugging:

  1. Never reuse the same UUID for two different things.
  2. Keep your UUIDs in a comment or a notes file. You will need them again when you write the phone-side test in Module 3.

Designing your gadget's service

Here is a design that fits a simple sensor gadget with one LED or buzzer. Read it before you build anything.

Item UUID Properties
Service 4fafc201-1fb5-459e-8fcc-c5c9c331914b none
Data characteristic beb5483e-36e1-4688-b7f5-ea07361b26a8 Notify
Control characteristic 7c9f1a2b-3d4e-4f5a-8b6c-9d0e1f2a3b4c Write, Write Without Response

The data characteristic is one-way, ESP32 to phone. It carries a short text payload such as temp=23.5.

The control characteristic is one-way, phone to ESP32. It carries a short command such as on or off.

Note what you deliberately do not have: a separate characteristic for every sensor, a Read property on the stream, or a second service. Small designs are easier to test and easier to demo.

When you turn on notifications, the phone writes to a hidden descriptor called the CCCD (Client Characteristic Configuration Descriptor). That is the phone's way of saying "yes, send me updates." Without that subscription, your ESP32 can notify all it likes and the phone stays silent.

Worked Examples

Example 1: Reading a GATT tree from the phone side

You open a BLE scanner app and connect to your board. The app shows:

Unknown Service (4fafc201-...)
  Unknown Characteristic (beb5483e-...)  Notify
  Unknown Characteristic (7c9f1a2b-...)  Write

The word Unknown only means the app has no friendly name for that UUID. The structure is exactly the design above: one service, two characteristics, one pushing and one receiving.

Example 2: From a sentence to a design

Goal sentence: "I want to see the temperature on my phone and switch a fan on and off."

  1. Temperature flows ESP32 to phone, repeatedly and unprompted, so it needs Notify.
  2. The fan command flows phone to ESP32, once per tap, so it needs Write.
  3. Both belong to the same feature, so they share one service.
  4. Two behaviours means two characteristics.

That gives exactly the layout in the table. A useful habit: count the arrows first, then name the UUIDs.

Example 3: Choosing properties when you are unsure

Ask two questions for each characteristic.

  • Does the phone need to push bytes into it? If yes, add Write.
  • Does the ESP32 need to push bytes out of it on its own schedule? If yes, add Notify.

If the answer to both is no, you probably do not need that characteristic at all. If the answer to both is yes on the same characteristic, stop and split it into two. Mixing directions on one characteristic makes the phone-side code awkward and hides bugs.

Discussion & Reflection

  1. Your gadget has three sensors. Would you send three notifications, or one notification with a combined text line? What does each choice cost you on the phone side?
  2. Why is advertising required if the phone will connect anyway? What breaks if the ESP32 stops advertising after the first connection?
  3. Which property would you choose if a lost command could damage hardware: Write or Write Without Response? Explain your reasoning.

Expert Corner

  • Twenty bytes is your default budget. With the default ATT MTU of 23, a notification carries about 20 bytes of payload. Keep early payloads short (t=23.5) and expand later.
  • Do not add Read to a streaming characteristic "just in case". It doubles the code paths you have to test and teaches you nothing.
  • Name your device uniquely. MyGadget-A3F2 beats ESP32 when three boards are on your desk.
  • Write the UUIDs down before you code. A typo in one hex digit produces a characteristic the phone simply never finds.
  • One service, few characteristics, clear names. Restraint now means a fast demo later.

Check your understanding with a few fill-ins:

  • The phone is the {{c1::central}} and the ESP32 is the {{c1::peripheral}}.

Hmm, that would need unique ids. Let me write them properly.

  • The phone plays the {{c1::central}} role and the ESP32 plays the {{c2::peripheral}} role.
  • A GATT service is a container that groups related {{c3::characteristics}}.
  • The properties that let the ESP32 push a value without being asked is {{c4::Notify}}.
  • The phone subscribes to notifications by writing to the hidden descriptor called the {{c5::CCCD}}.
  • Custom services must use {{c6::128}}-bit UUIDs, because the short 16-bit ones are reserved by the Bluetooth SIG.

Flashcards

[
  { "front": "In a BLE connection, what is the central?", "back": "The device that scans and starts the connection, here the phone." },
  { "front": "In a BLE connection, what is the peripheral?", "back": "The device that advertises and waits to be connected, here the ESP32." },
  { "front": "What does GATT stand for and define?", "back": "Generic Attribute Profile: the agreed structure for services, characteristics and values after connection." },
  { "front": "What is a GATT service?", "back": "A container that groups related characteristics under one UUID." },
  { "front": "What is a GATT characteristic?", "back": "One single data value plus the properties that say how it may be used." },
  { "front": "What does the Notify property do?", "back": "Lets the ESP32 push a value whenever it wants, with no acknowledgement from the phone." },
  { "front": "What does the Indicate property do?", "back": "Same as Notify but each message must be acknowledged, so it is safer and slower." },
  { "front": "What does the Write property do?", "back": "Lets the phone send bytes to the ESP32, which receives them." },
  { "front": "What is the CCCD?", "back": "Client Characteristic Configuration Descriptor: the phone writes to it to subscribe to notifications." },
  { "front": "Why must custom services use 128-bit UUIDs?", "back": "The short 16-bit UUIDs are reserved by the Bluetooth SIG for standard services." },
  { "front": "What happens to advertising once the phone connects?", "back": "It stops; the two devices move into a connection." },
  { "front": "In the lesson's design, which property does the data characteristic use?", "back": "Notify only, ESP32 to phone." },
  { "front": "In the lesson's design, which property does the control characteristic use?", "back": "Write and Write Without Response, phone to ESP32." },
  { "front": "Roughly how much payload fits in one notification at the default ATT MTU?", "back": "About 20 bytes, with a default MTU of 23." }
]

Summary & Next Steps

You now know the contract: a phone (central) finds your ESP32 (peripheral) through advertising, connects, and reads a GATT tree of one service holding one data characteristic and one control characteristic. You also know which properties belong on each side: Notify out, Write in.

In the next lesson you will make the data characteristic actually talk. You will wire a sensor reading into a notification, watch it appear on the phone, and confirm the CCCD subscription is what makes it flow.