In this guide
Bluetooth Low Energy is the reason your phone can find a lost tag, a museum app knows which exhibit you are standing in front of, and a warehouse tracks pallets without scanning barcodes. The underlying trick in all of these is the beacon: a tiny device that does nothing but shout "I am here" over and over, and lets receivers figure out the rest.
The ESP32 is one of the cheapest ways to build a real BLE beacon, and it doubles as a teaching tool for how BLE actually works — advertising, services, characteristics, and the difference between a beacon and a connection. This guide walks through BLE concepts the way you need them for a student project, then builds a working ESP32 beacon you can detect with your phone.
What BLE is (and is not)
Bluetooth Low Energy is not just "Bluetooth but low power." Classic Bluetooth (the kind that streams audio to headphones) maintains a continuous connection with relatively high throughput. BLE was designed from scratch for a different job: tiny packets, sent infrequently, with the radio asleep almost all the time. A beacon might transmit a 30-byte advertisement every second and spend the other 999 milliseconds in deep sleep — which is why coin-cell beacons run for years.
Key vocabulary you will use constantly:
| Term | Meaning |
|---|---|
| Advertisement | A broadcast packet any nearby device can hear — no pairing, no connection |
| Central / Peripheral | Central scans and connects (your phone); peripheral advertises (your beacon) |
| GATT | The data model used once connected: services contain characteristics |
| Service | A grouping of related data (e.g. battery service, temperature service) |
| Characteristic | A single data point you can read, write, or subscribe to notifications on |
| UUID | The 128-bit (or 16-bit shorthand) identifier for a service or characteristic |
The conceptual split that matters: advertising is for discovery, GATT is for conversation. Beacons live entirely in the advertising world — they never accept connections. That simplicity is the whole point.
Beacon formats: iBeacon and Eddystone
Two standard formats dominate, and your ESP32 can broadcast either:
iBeacon (Apple's format) carries three identifiers: a UUID (identifies your deployment, e.g. one museum), a Major value (identifies a group, e.g. one floor), and a Minor value (identifies the specific beacon, e.g. one exhibit). Receivers compute proximity from the received signal strength (RSSI): immediate, near, far, or unknown.
Eddystone (Google's format) comes in frame types: UID (like iBeacon's identifiers), URL (broadcasts an actual web URL — a beacon can advertise a webpage with no app needed), and TLM (telemetry: battery voltage, temperature, uptime).
For student projects, iBeacon is the simplest to detect — both iOS and Android have scanner apps that decode it out of the box. Eddystone-URL is the most fun to demo: a beacon that makes a URL appear on nearby phones with no app install.
RSSI and distance: the honest version
Every beacon tutorial shows distance estimation from RSSI. Here is what they often skip: RSSI-to-distance is approximate, not a measurement. The standard formula uses the signal strength measured at 1 meter (the "measured power" value the beacon broadcasts) and a path-loss exponent:
- In free space with line of sight, it is roughly right.
- Through walls, near metal, near human bodies (which absorb 2.4 GHz), or with multipath reflections, it can be off by meters.
Treat beacon ranging as proximity zones (immediate / near / far), not as a tape measure. Projects that need real positioning use multiple beacons and fingerprinting or trilateration, and even those report zones, not coordinates. Design your application around "which beacon am I closest to" and it will work; design it around "I am 2.37 meters from beacon A" and it will disappoint.
Building the beacon on ESP32
The Arduino BLE library makes this straightforward. A minimal iBeacon broadcaster:
#include <BLEDevice.h>
#include <BLEBeacon.h>
BLEAdvertising *pAdvertising;
void setup() {
BLEDevice::init("Classroom-Beacon-01");
pAdvertising = BLEDevice::getAdvertising();
BLEBeacon beacon;
beacon.setManufacturerId(0x4C00); // Apple
beacon.setProximityUUID(BLEUUID("a1b2c3d4-e5f6-7890-abcd-ef1234567890"));
beacon.setMajor(1); // e.g. floor number
beacon.setMinor(101); // e.g. room number
beacon.setSignalPower(-59); // measured RSSI at 1 meter
BLEAdvertisementData adv;
adv.setFlags(0x06);
std::string svc = beacon.getData();
adv.setManufacturerData(svc);
pAdvertising->setAdvertisementData(adv);
pAdvertising->start();
}
void loop() {
delay(1000); // beacon runs on its own; CPU can sleep
}
Generate your own proximity UUID (any UUID generator works) — using someone else's means your beacon collides with theirs in scanner apps. The signal power value should be the actual RSSI measured 1 meter from your beacon; -59 is a placeholder, and calibrating it with a phone app noticeably improves proximity accuracy.
Note: a beacon does not need WiFi, does not connect to anything, and barely uses the CPU. This is the ideal workload to combine with deep sleep between advertisements if you want months of battery life — advertise for a few milliseconds, sleep for a second, repeat.
Reading beacons: the scanner side
A beacon project has two halves. The scanner (usually a phone app, or a second ESP32) listens for advertisements and acts on them. On the ESP32, scanning looks like this:
#include <BLEDevice.h>
class AdvertisedCallbacks : public BLEAdvertisedDeviceCallbacks {
void onResult(BLEAdvertisedDevice advertised) {
if (advertised.haveManufacturerData()) {
int rssi = advertised.getRSSI();
// parse beacon fields from manufacturer data, act on proximity
}
}
};
void setup() {
BLEDevice::init("");
BLEScan *scan = BLEDevice::getScan();
scan->setAdvertisedDeviceCallbacks(new AdvertisedCallbacks());
scan->setActiveScan(true);
scan->start(30); // scan for 30 seconds
}
For demos, a phone scanner app (nRF Connect, or any BLE scanner) is faster than writing scanner firmware — you see your beacon's UUID, major/minor, and live RSSI within seconds of powering the ESP32.
Beacon vs connected BLE: choosing the pattern
Students often conflate beacons with BLE connections. Decide based on direction of data flow:
| Pattern | Data direction | Example | Needs pairing? |
|---|---|---|---|
| Beacon (advertising only) | Beacon → everyone nearby | Indoor navigation, attendance check-in | No |
| GATT peripheral | Phone ↔ device, two-way | Sensor that a phone reads on demand | Sometimes |
| GATT + notifications | Device → phone, pushed | Live heart-rate-style streaming | Usually |
If receivers only need to know "which beacon is near me," stay in beacon mode — it is simpler, lower power, and supports unlimited receivers. Move to GATT connections only when the phone must send data back to the device or read changing sensor values on demand.
Practical project ideas that actually work
- Lab equipment finder: beacons on shared equipment; a phone app shows the nearest oscilloscope or multimeter.
- Automatic attendance: a classroom beacon; student phones log the beacon's major/minor with a timestamp when in range.
- Museum-style exhibit guide: each project demo station gets a beacon; visitors' phones show that station's details.
- Asset zone tracking: beacons at zone entrances; a fixed ESP32 scanner logs when tagged items move between zones.
All four are demoable with one ESP32 and a phone, and all four teach real BLE concepts rather than just blinking an LED.
Calibrating proximity and planning a beacon deployment
The difference between a beacon demo that works and one that confuses is calibration. Out of the box, your beacon advertises a placeholder measured-power value, and every phone model reports RSSI slightly differently. A 30-minute calibration pass fixes most of it:
- Place the beacon where it will actually live — height, orientation, and nearby metal all change the RF picture.
- Stand exactly 1 meter away with your scanner app and note the RSSI reading. Repeat at 2, 3, and 5 meters.
- Set the beacon's advertised signal power to your 1-meter reading, and record the others as your empirical distance table.
- Repeat with a second phone model if your demo audience brings their own devices.
Then design the application around zones, not distances: immediate (stronger than your 1m reading minus a margin), near (between the 1m and 3m readings), far (weaker). Add hysteresis — require the signal to stay in a zone for a few seconds before switching — because RSSI jitters constantly. A zone indicator that flickers between near and far every second looks broken even when the underlying data is fine.
Deployment planning for multi-beacon projects:
- Space beacons so their near zones barely overlap; overlapping near zones make nearest-beacon logic ambiguous.
- Mount beacons high and in the open — inside metal enclosures or behind pillars kills range unpredictably.
- Write down a labeled major/minor scheme before flashing: building/floor/room beats random numbers when you debug.
- Battery-plan the advertising interval: 100 ms is responsive but hungry; 1000 ms is the usual compromise for months of coin-cell life. Faster intervals help phone-side detection speed, slower intervals help battery — pick per use case.
Finally, test with the actual phones your audience uses, in the actual venue, with the venue's WiFi running. The 2.4 GHz band is shared, and a room full of laptops changes the RF environment measurably. A beacon system calibrated in an empty lab and demoed in a crowded hall will behave differently — repeating the calibration on site takes 20 minutes and saves the demo.
Common mistakes checklist
- Expecting meter-accurate distance. RSSI gives zones, not coordinates. Design for nearest-beacon logic.
- Copying someone else's UUID. Scanner apps group by UUID — collisions make your beacon indistinguishable from theirs.
- Leaving the default TX power. Maximum transmit power wastes battery; the lowest power that covers your area is correct.
- Scanning with the phone in a pocket. Human bodies attenuate 2.4 GHz significantly — demo with the phone in hand.
- Forgetting beacons are one-way. If your app needs to send commands to the device, you need a GATT connection, not a beacon.
- Testing only in one room. Metal furniture, WiFi routers, and microwaves all share the 2.4 GHz band — test where you will demo.
Where to go from here
- Control Arduino/ESP32 from an Android app over Bluetooth/WiFi — the natural next step when your beacon project grows into a two-way phone app.
- ESP32 low-power battery design guide — for beacons that must run months on a coin cell or small Li-ion.
- Arduino to ESP32 migration guide — if you are moving an existing Arduino Bluetooth project to the ESP32.
- More wireless project ideas in the IoT & Embedded branch hub.