# 📡 Stream Sensor Data to the Phone with BLE Notifications

## Learning Objectives

By the end of this lesson you can:

- Explain why a notification is different from a read, and when to use each.
- Describe the CCCD (UUID `0x2902`) and how the phone uses it to switch notifications on.
- Set the correct characteristic properties for a notifying data characteristic.
- Read a sensor value in `loop()` and push it to a subscribed phone without blocking the BLE stack.
- Choose a sensible update interval and payload size for your gadget.

## Quick Review

Last lesson you designed the contract: the phone (central) finds the ESP32 (peripheral) through advertising, connects, and walks a GATT tree with one custom service holding a **data characteristic** and a **control characteristic**. Each characteristic has a UUID, a set of **properties** (what is allowed), and a **value** (the bytes).

So far the phone could only pull that value with a read. In this lesson we make the ESP32 push it instead.

## Theory

### Read versus notify

A read is a question the phone asks. A notification is the ESP32 speaking first, with no question. That matters because a phone does not poll in the background reliably: apps get suspended, and polling drains both batteries. Notifications let the ESP32 say "here is a new reading" the moment it has one.

| | Read | Notify |
| --- | --- | --- |
| Who starts it | Phone | ESP32 |
| Needs subscription | No | Yes, via CCCD |
| Acknowledged | Yes (the response is the data) | No, fire and forget |
| Typical use | Config, calibration, one-off values | Streaming sensor data |

If you need delivery confirmation, BLE also offers **indications**, which wait for an acknowledgement from the phone. They are slower, so we use notifications for streaming.

### The CCCD is the on/off switch

A phone will not accept notifications it never asked for. The permission lives in a small descriptor attached to your characteristic: the **Client Characteristic Configuration Descriptor (CCCD)**, UUID `0x2902`, holding 2 bytes.

```cloze
When the phone writes {{c1::0x0001}} to the CCCD, notifications are enabled, and {{c2::0x0000}} turns them off again.
The CCCD descriptor has the UUID {{c3::0x2902}}.
A notification is {{c4::unacknowledged}} (fire and forget), while an indication waits for a confirmation.
The default ATT MTU of 23 bytes leaves {{c5::20}} bytes of usable payload per notification.
```

With the ESP32 Arduino BLE library you add this descriptor yourself with `new BLE2902()`. Without it, the phone sees no switch to flip, and `notify()` has nowhere to go. The NimBLE library creates the descriptor automatically, which is one reason some people switch to it.

### Properties matter too

The characteristic still needs the correct property bits, or the phone will refuse to subscribe:

| Property | Bit |
| --- | --- |
| Read | `0x02` |
| Notify | `0x10` |
| Indicate | `0x20` |
| Write | `0x08` |

Read plus notify is `0x12`. In code you rarely type the byte, you OR the constants: `PROPERTY_READ | PROPERTY_NOTIFY`.

### The streaming loop

```mermaid
flowchart LR
  Sensor["🌡️<br/>Sensor read"]
  Value["📦<br/>Characteristic value"]
  Check["🔗<br/>Client subscribed?"]
  Phone["📱<br/>Phone app"]
  Drop["🚫<br/>Skip this cycle"]
  Sensor --> Value --> Check
  Check -->|Yes| Phone
  Check -->|No| Drop
```

Three rules make this loop behave:

1. **Never call `delay()` to set the sample rate.** `delay()` freezes everything, including the BLE stack that must answer the phone. Use `millis()` and a timestamp instead.
2. **Check that a client is connected** before you build and send data.
3. **Set the value, then notify.** `setValue()` fills the characteristic; `notify()` transmits it.

## Worked Examples

### Example 1: Trace one notification by hand

1. `millis()` reaches the 200 ms mark, so the loop decides it is time to sample.
2. `readTemperature()` returns `23.5`.
3. `setValue()` copies the 4 bytes of that float into the characteristic value.
4. `notify()` checks whether any connected client has written `0x0001` to the CCCD.
5. If yes, the ESP32 sends one ATT notification holding those 4 bytes.
6. The phone's `onCharacteristicChanged` callback fires, and the app decodes 4 bytes back into a float.

Stop at step 4 and notice the failure case: on a fresh boot with no phone, the ESP32 still burns cycles building values nobody receives. That is why the connection check comes first.

### Example 2: Property byte math

You want a characteristic the phone can read once (to show the current value on connect) and also subscribe to.

- Read = `0x02`
- Notify = `0x10`
- `0x02 | 0x10 = 0x12`

### Example 3: Sizing the stream

Your gadget samples a temperature every 200 ms and sends a 4-byte float.

- Notifications per second: $1000 / 200 = 5$
- Payload per second: $5 \times 4 = 20$ bytes

That is trivial for BLE. BLE's default maximum payload per packet is 20 bytes, so even a 5 Hz stream of a small struct fits with room to spare. If you needed 100 bytes per reading, you would negotiate a larger MTU or reconsider the design.

### Example 4: The actual code

```cpp
// setup(): Read + Notify, and the CCCD the phone writes to
BLECharacteristic *dataChar = dataService->createCharacteristic(
    DATA_CHAR_UUID,
    BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY);
dataChar->addDescriptor(new BLE2902());

// loop(): non-blocking sampling
unsigned long lastSend = 0;
const unsigned long SEND_EVERY_MS = 200;

void loop() {
  if (deviceConnected && millis() - lastSend >= SEND_EVERY_MS) {
    lastSend = millis();
    float temperature = readTemperature();
    dataChar->setValue((uint8_t *)&temperature, sizeof(temperature));
    dataChar->notify();
  }
}
```

The `notify()` return value is a boolean: `false` usually means no client is subscribed, so log it while you are developing.

## Discussion & Reflection

- Your phone stops showing values after the screen locks. Is that a bug in your firmware, or an app-side subscription problem? How would you tell the two apart from the serial log?
- What is the slowest update rate your gadget could get away with and still feel live to a user?
- If you sent a temperature as text (`"23.5"`) instead of a float, what gets simpler and what gets bigger?

## Expert Corner

Common mistakes and rules of thumb:

- **Notifying before setting a value** sends the previous reading. Always `setValue()` first.
- **Byte order is your contract.** A raw float is little-endian on the ESP32; a phone decoding big-endian shows nonsense. Either document the format, scale to an integer (temperature times 100 as `int16_t`), or send text for readability while debugging.
- **Never do sensor work inside a BLE callback.** Callbacks run on the BLE task; a slow read there can drop the connection. Copy the incoming command to a variable and act on it in `loop()`.
- **Do not notify faster than the connection interval.** A phone typically uses 30 to 50 ms. Sampling at 1 ms and notifying every time just queues data you cannot deliver.
- **The first notification after subscribing is often lost on Android.** Send one immediate update when the client subscribes, or accept that the phone may need a moment.
- Keep notifications small. Default MTU gives you 20 bytes; anything larger needs negotiation and a phone that supports it.

## Summary & Next Steps

You now know the push half of the gadget: a data characteristic with Read and Notify, a CCCD at `0x2902` that the phone writes to subscribe, a `millis()`-driven loop that reads a sensor and calls `setValue()` then `notify()`, and a payload small enough to fit in one packet.

Next lesson flips the arrow: the phone writes to the **control characteristic**, and your firmware learns to receive commands, validate them, and change what the gadget is doing.

```flashcard
[
  { "front": "How does a notification differ from a read?", "back": "The ESP32 pushes the value on its own; the phone does not have to ask each time." },
  { "front": "Which descriptor lets the phone switch notifications on?", "back": "The Client Characteristic Configuration Descriptor (CCCD), UUID 0x2902." },
  { "front": "What 2-byte value does the phone write to the CCCD to enable notifications?", "back": "0x0001. The value 0x0000 disables them." },
  { "front": "Which property bit must be set for notifications?", "back": "Notify, bit value 0x10." },
  { "front": "Read (0x02) plus Notify (0x10) equals which property byte?", "back": "0x12." },
  { "front": "Default ATT MTU and usable notification payload?", "back": "23 bytes MTU, about 20 bytes of usable payload after 3 bytes of overhead." },
  { "front": "Why use millis() instead of delay() in the streaming loop?", "back": "delay() blocks the BLE stack, so connection events and incoming writes get missed." },
  { "front": "What must you check before calling notify()?", "back": "That a client is connected and has subscribed to the characteristic." },
  { "front": "What is the difference between setValue() and notify()?", "back": "setValue() fills the characteristic value; notify() transmits it to the subscribed client." },
  { "front": "Notification versus indication?", "back": "A notification is unacknowledged; an indication waits for a confirmation from the phone." },
  { "front": "Why avoid notifying faster than the connection interval?", "back": "The phone typically uses 30 to 50 ms; faster sends just queue undeliverable data." },
  { "front": "What does a false return from notify() usually mean?", "back": "No client is subscribed, so nothing was sent." },
  { "front": "How should a float sensor value be encoded for the phone?", "back": "Both ends must agree on the format: raw little-endian bytes, a scaled integer, or text." },
  { "front": "Where should the sensor read happen, in a BLE callback or in loop()?", "back": "In loop() with a millis() timer, so callbacks stay short and the connection stays healthy." }
]
```
