Skip to content
Buy 10+ on select items — save 10% auto-applied
Free US shipping on orders $35+
Order by 3pm ET — ships same-day from the US
Skip to main content

ESP32 BLE UART: Phone Control and Notifications | ShillehTek

September 13, 2026 6 views

ESP32 BLE UART: Phone Control and Notifications | ShillehTek
Project

Build an ESP32 BLE UART (NUS) server to control an output from a phone and receive live sensor notifications, using a simple terminal app and ShillehTek parts.

45 min Intermediate5 parts

Project Overview

ESP32 BLE UART: In this project, you use an ESP32 with the Nordic UART Service (NUS) to connect from a phone app over Bluetooth Low Energy, send text commands to switch an output, and receive a live analog sensor reading every second via notifications.

Classic Bluetooth Serial is easy, but it only works with Android. Bluetooth Low Energy works with iPhones too, uses less power, and is what most modern wearables and smart locks use. Once you understand services, characteristics, and notifications, the rest of BLE becomes much easier to approach.

  • Time: ~45 minutes
  • Skill level: Intermediate
  • What you will build: A BLE server with the standard Nordic UART Service, phone-to-board commands, and board-to-phone notifications.
ESP32 BLE Nordic UART Service connected to a phone BLE terminal app for UART-style text send and notify
Services and characteristics are the building blocks of BLE devices.

Parts List

From ShillehTek

External

  • A phone with a BLE terminal app: "nRF Connect" (Android/iOS) or "Serial Bluetooth Terminal" (Android, BLE mode)
  • A potentiometer or any analog sensor on GPIO 32 for the live reading

Note: BLE data is organized as services containing characteristics. The Nordic UART Service (NUS) is a common pattern with one RX characteristic (phone writes to it) and one TX characteristic (board notifies through it). Using its well-known UUIDs means off-the-shelf terminal apps can recognize your board quickly.

Step-by-Step Guide

Step 1 - Wire an Output and an Input

Goal: Create something to control (output) and something to report (input).

What to do: Use the onboard LED on GPIO 2 (or a relay IN pin) as your output.

For the input, wire a potentiometer wiper to GPIO 32, with the other ends going to 3V3 and GND. Any suitable sensor voltage works for the analog read.

Expected result: One GPIO output and one ADC input are ready.

Step 2 - Install a BLE Terminal App

Goal: Get a way to send and view BLE data from your phone.

What to do: Install nRF Connect (works on both iOS and Android). It can scan for BLE devices, connect, browse services, write to characteristics, and subscribe to notifications.

Expected result: The app can scan for nearby BLE devices.

Step 3 - Upload the Sketch and Test NUS

Goal: Turn the ESP32 into a BLE UART server (NUS) that accepts commands and sends sensor updates.

What to do: Upload the following sketch, then open your BLE terminal app to connect and interact with the characteristics.

Code:

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

// Nordic UART Service UUIDs (recognized by most BLE terminal apps)
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
#define RX_UUID      "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"   // phone -> board (write)
#define TX_UUID      "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"   // board -> phone (notify)

const int OUT = 2, SENSOR = 32;
BLECharacteristic* tx;
bool connected = false;

class ServerCB : public BLEServerCallbacks {
  void onConnect(BLEServer*)    { connected = true;  Serial.println("phone connected"); }
  void onDisconnect(BLEServer* s) { connected = false; s->startAdvertising(); }   // be findable again
};

class RxCB : public BLECharacteristicCallbacks {
  void onWrite(BLECharacteristic* c) {
    String msg = c->getValue().c_str();          // works on ESP32 core 2.x and 3.x
    msg.trim(); msg.toLowerCase();
    Serial.println("got: " + msg);
    if (msg == "on"  || msg == "a") digitalWrite(OUT, HIGH);
    if (msg == "off" || msg == "b") digitalWrite(OUT, LOW);
  }
};

void setup() {
  Serial.begin(115200);
  pinMode(OUT, OUTPUT);

  BLEDevice::init("ShillehTek-ESP32");           // the name you'll see in the app
  BLEServer* server = BLEDevice::createServer();
  server->setCallbacks(new ServerCB());
  BLEService* svc = server->createService(SERVICE_UUID);

  tx = svc->createCharacteristic(TX_UUID, BLECharacteristic::PROPERTY_NOTIFY);
  tx->addDescriptor(new BLE2902());              // lets the phone subscribe to notifications

  BLECharacteristic* rx = svc->createCharacteristic(RX_UUID, BLECharacteristic::PROPERTY_WRITE);
  rx->setCallbacks(new RxCB());

  svc->start();
  server->getAdvertising()->addServiceUUID(SERVICE_UUID);
  server->startAdvertising();
  Serial.println("advertising...");
}

void loop() {
  if (connected) {
    char buf[24];
    snprintf(buf, sizeof buf, "sensor=%d\n", analogRead(SENSOR));
    tx->setValue(buf);
    tx->notify();                                // push to the phone
  }
  delay(1000);
}

In nRF Connect, scan and connect to ShillehTek-ESP32. Expand the Nordic UART Service.

Tap the down-arrow on the TX characteristic to subscribe, then tap the up-arrow on RX, choose text, and send on.

Expected result: The LED turns on; sensor=... values arrive once a second under TX and change as you turn the potentiometer. Send off to turn it off.

Step 4 - Understand What You Just Built

Goal: Connect the code to core BLE concepts.

What to do: Advertising is the board broadcasting its name and service so the phone can find it. Connecting stops advertising, which is why the disconnect callback restarts it.

A characteristic with WRITE is a mailbox the phone can write bytes into. A characteristic with NOTIFY is a stream the phone can subscribe to. The 2902 descriptor is the subscribe toggle.

Expected result: You can browse a BLE device's services and characteristics and make informed guesses about what they do.

Step 5 - Make It Yours

Goal: Extend the pattern beyond a basic terminal demo.

What to do: You can send JSON like {"t":23.5,"h":41} and parse it on the phone, build a small Web Bluetooth page (Chrome on Android/desktop), or add a second characteristic for another control like a servo angle.

For battery builds, lower the advertising interval and sleep between notifications. BLE is designed for low-power devices.

Expected result: A BLE gadget with your own protocol built on the NUS pattern.

Conclusion

You built an ESP32 BLE Nordic UART Service that accepts phone commands via a write characteristic and sends sensor updates via notifications. With advertising, services, characteristics, and descriptors mapped to real behavior, you now have a reusable template for many BLE projects.

Want the exact parts used in this build? Grab them from ShillehTek.com. If you want help customizing this project or building something for your product, check out our IoT consulting services.

Credits

All photos and images in this tutorial are credited to Timothy Woo (Botletics) on Hackster.io. The original guide by Timothy Woo served as the reference for this ShillehTek version (shared under CC BY-SA). We thank him for his excellent work in the maker community.

Parts for this build

Everything used in this tutorial. Uncheck what you already have.

4 of 5 in stock
0 parts selected $0.00