# 📱 Test Your Gadget with a Phone App

## Quick Review

In the last module you built both directions of your gadget's GATT service: a **sensor characteristic** with `PROPERTY_NOTIFY` that pushes readings out, and a **control characteristic** with `PROPERTY_WRITE` whose `onWrite` callback receives commands. Both directions work in code. Today you prove they work in the real world, with a phone in your hand.

## Learning Objectives

By the end of this lesson you can:

- Scan for your ESP32 and connect to it from a phone BLE scanner app.
- Subscribe to the sensor characteristic and watch notifications arrive.
- Send a text command to the control characteristic and confirm it on the ESP32.
- Read the Serial Monitor alongside the phone and match events on both sides.

## Worked Example: One Complete Test Session

We will use **nRF Connect for Mobile** (Nordic Semiconductor), which is free on Android and iOS. LightBlue works too, but the icons differ slightly.

```mermaid
flowchart LR
    Scan["📡<br/>Scan"] --> Conn["🔗<br/>Connect"]
    Conn --> Sub["🔔<br/>Subscribe (CCCD)"]
    Sub --> Watch["📈<br/>Watch notifications"]
    Watch --> Write["⌨️<br/>Write command"]
    Write --> Serial["🖥️<br/>Check Serial Monitor"]
```

```steps
1. Flash your gadget and open the Serial Monitor at 115200 baud. You should see the advertising line, for example `Advertising as ESP32_Gadget`.
2. Install nRF Connect, then grant Bluetooth permission. On Android 12+ this is the "Nearby devices" permission; without it the scan returns nothing.
3. Open the SCAN tab. Find your device by its advertised name and note the RSSI value, for example `-52 dBm`.
4. Tap CONNECT. The Serial Monitor should print a connect event with the phone's address.
5. Expand your custom service by its UUID, then expand the sensor characteristic. Tap the three-arrow (subscribe) icon.
6. Watch the value field update every second. This is a notification arriving over BLE.
7. Expand the control characteristic, tap the up-arrow (write) icon, choose **UTF-8** as the format, type `LED_ON`, and send it as **Write** (with response).
8. Confirm on the Serial Monitor that `onWrite` fired and printed your command. Then disconnect and check that the ESP32 starts advertising again.
```

If all eight steps pass, your gadget is functionally complete. That is a real milestone.

> [!NOTE]
> Subscribe (step 5) works by writing `0x01 0x00` to the **CCCD**, the Client Characteristic Configuration Descriptor with UUID `0x2902`. That write is what triggers your characteristic's `onSubscribe` callback. Without it, `notify()` has nowhere to go.

## Guided Practice: Run Your Own Test Pass

Do the session yourself, once with the serial monitor open and once without, and record what you see. Write your answers down; you will reuse this log in the capstone demo.

1. **Scan pass.** Note the advertised name and RSSI at arm's length and at 5 metres. Which one is closer to 0 dBm?
2. **Subscribe pass.** Before subscribing, confirm the value field is empty or static. After subscribing, confirm it changes. This proves the notifications come from your `notify()` call, not from the app polling.
3. **Command pass.** Send three commands: one valid (`LED_ON`), one valid (`LED_OFF`), one nonsense (`BANANA`). Your `onWrite` callback should handle the unknown case without rebooting.
4. **Reconnect pass.** Disconnect, then connect again and re-subscribe. Do notifications resume?
5. **Cross-check pass.** If you have a second phone with a different OS, repeat steps 1 to 3. iOS and Android behave differently in the details.

Fill in this log as you go:

| Check | Expected result | Your result |
| --- | --- | --- |
| Scan finds device | Name visible, RSSI between -30 and -80 dBm | |
| Connect | Serial prints client address | |
| Subscribe | Notifications start within 1 to 2 seconds | |
| Write `LED_ON` | `onWrite` prints the command | |
| Disconnect | Advertising restarts | |

Quick self-check with cloze:

- The characteristic that pushes readings to the phone must have the property {{c1::NOTIFY}}.
- Tapping subscribe in the app writes to the {{c2::CCCD}} descriptor, UUID {{c3::0x2902}}.
- A {{c4::Write with response}} waits for the ESP32 to acknowledge; a write without response does not.
- The ESP32's side of every test is visible in the {{c5::Serial Monitor}} at 115200 baud.
- Signal strength is reported as {{c6::RSSI}} in dBm, where values closer to 0 are stronger.

## Common Mistakes and Troubleshooting

| Symptom | Likely cause | Fix |
| --- | --- | --- |
| Device never appears in the scan list | Advertising not started, or Bluetooth permission denied | Check the Serial Monitor for the advertising line, then re-grant permissions |
| Connects but the service list is empty | Android cached an old GATT table after reflashing | Turn phone Bluetooth off and on, or "forget" the device, then rescan |
| Subscribe icon is greyed out | Characteristic has no `PROPERTY_NOTIFY` | Recheck the property flags in your sketch |
| Subscribed, but no values arrive | Not calling `setValue()` before `notify()`, or notifying with no delay | Set the value, call `notify()`, then `delay(1000)` |
| Command sent, nothing happens | Sent as hex instead of UTF-8, or wrote to the wrong characteristic | Choose UTF-8 format and confirm you wrote the control characteristic |
| Values look like garbage | Wrong display format in the app | Switch between UTF-8, hex, and signed integer views |

> [!TIP]
> Use **Write with response** while testing. It gives you an immediate error if the write is rejected, which is far easier to debug than a silent write without response.

## Expert Corner

- Keep the Serial Monitor open for every test. The phone shows you the client view; the serial log shows you the truth.
- Test at arm's length first. If it fails there, range is not your problem.
- Name the device with a version suffix, such as `ESP32_Gadget_v2`, so you never connect to a stale cached device.
- Test both write types. Write without response is faster and is what production apps often use, but only after with-response works.
- Log the handle numbers of both characteristics in your notes. They are handy when a demo goes sideways.

## Summary and Next Steps

You scanned, connected, subscribed to notifications, and sent commands from a phone, and you matched every phone-side event to a line in the Serial Monitor. That is the complete two-way loop, verified end to end.

One thing you probably noticed: connections drop. Phones go to sleep, users walk away, and the app is backgrounded. In the next lesson, **Handle Disconnects and Common BLE Pitfalls**, you will make your gadget survive all of that and come back cleanly.

```flashcard
[
  { "front": "Which free app is the standard BLE scanner on Android?", "back": "nRF Connect for Mobile by Nordic Semiconductor." },
  { "front": "Which app is commonly used as an iOS BLE scanner?", "back": "LightBlue, or nRF Connect for iOS." },
  { "front": "What do you tap to start receiving notifications?", "back": "The subscribe icon (three arrows) on the notify characteristic, which writes to the CCCD." },
  { "front": "Which descriptor enables notifications, and what is its UUID?", "back": "The Client Characteristic Configuration Descriptor (CCCD), UUID 0x2902." },
  { "front": "What happens on the ESP32 when the phone subscribes?", "back": "The CCCD write triggers the characteristic's onSubscribe callback." },
  { "front": "Which write type waits for an acknowledgement from the ESP32?", "back": "Write with response (Write Request)." },
  { "front": "Where do you see the ESP32 side of a test?", "back": "In the Serial Monitor at 115200 baud." },
  { "front": "What does RSSI measure?", "back": "Signal strength in dBm; closer to 0 is stronger, around -70 dBm or better at arm's length." },
  { "front": "Why does Android sometimes show an empty service list after reflashing?", "back": "Android caches the GATT table; toggle Bluetooth or forget the device to refresh it." },
  { "front": "Which permission does Android 12+ need for BLE scanning?", "back": "The Nearby devices permission (BLUETOOTH_SCAN and BLUETOOTH_CONNECT)." },
  { "front": "Why check the subscription flag before calling notify()?", "back": "It confirms a client actually subscribed; notifying otherwise wastes power and can error." },
  { "front": "What form does a written command arrive in on the ESP32?", "back": "Raw bytes; decode them with the value pointer and length." },
  { "front": "Which property must the control characteristic declare to receive commands?", "back": "PROPERTY_WRITE." }
]
```
