Article
Build a Bluetooth Presence Detector for Smart Homes with ESP32
📶
Smart home automation based on phone GPS drains battery and needs a stable internet connection; automation based on a motion sensor can’t tell who’s actually home. A cheaper and more reliable approach: most phones constantly broadcast Bluetooth Low Energy (BLE) signals even when not paired to anything — an ESP32 can scan for these signals within a few meters’ range and infer “someone’s home” or “everyone left” with zero app or phone-side configuration.
How it works
- The ESP32 periodically runs a BLE scan for nearby broadcasting devices, collecting each device’s MAC address and signal strength (RSSI).
- It compares each scanned MAC address against a pre-known list (the household members’ phones) — modern iOS/Android randomize their broadcast MAC over time, which is a real limitation covered in the pitfalls section below.
- If a known MAC shows up consistently across several scans, the ESP32 marks that device “present”; if it’s absent across many consecutive scans (beyond a timeout window), it’s marked as having left.
- Presence state is published over MQTT to Home Assistant or a self-hosted broker, which then drives automations: turn on lights/AC when someone arrives, shut everything off when nobody’s left.
Parts list
- An ESP32 (any variant with BLE support, including ESP32-C3/S3)
- A stable 5V power supply (USB adapter), since the device runs continuously 24/7
- An MQTT broker (self-hosted Mosquitto, or the one bundled with Home Assistant)
- The household members’ phones, to collect their BLE MAC addresses (use a BLE scanner app like nRF Connect to view the MAC while Bluetooth is on)
Sample code
#include <WiFi.h>
#include <PubSubClient.h>
#include <BLEDevice.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>
const char* WIFI_SSID = "your-wifi";
const char* WIFI_PASS = "your-wifi-password";
const char* MQTT_HOST = "192.168.1.10";
WiFiClient espClient;
PubSubClient mqtt(espClient);
BLEScan* bleScan;
// Phone MAC address to track, replace with the real one
const char* KNOWN_MAC = "aa:bb:cc:dd:ee:ff";
bool present = false;
unsigned long lastSeen = 0;
const unsigned long TIMEOUT_MS = 60000; // 1 minute unseen -> treat as away
void connectWifiMqtt() {
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) delay(500);
mqtt.setServer(MQTT_HOST, 1883);
while (!mqtt.connected()) {
mqtt.connect("esp32-presence");
delay(500);
}
}
void setup() {
Serial.begin(115200);
connectWifiMqtt();
BLEDevice::init("");
bleScan = BLEDevice::getScan();
bleScan->setActiveScan(false); // passive scan, easier on the phone's battery
}
void loop() {
if (!mqtt.connected()) connectWifiMqtt();
mqtt.loop();
BLEScanResults* results = bleScan->start(5, false); // scan 5 seconds per cycle
bool foundNow = false;
for (int i = 0; i < results->getCount(); i++) {
BLEAdvertisedDevice device = results->getDevice(i);
if (device.getAddress().toString() == KNOWN_MAC) {
foundNow = true;
lastSeen = millis();
break;
}
}
bleScan->clearResults();
bool nowPresent = (millis() - lastSeen) < TIMEOUT_MS;
if (nowPresent != present) {
present = nowPresent;
mqtt.publish("home/presence/phone1", present ? "home" : "away", true);
Serial.println(present ? "Arrived home" : "Left home");
}
delay(10000); // pause between scan cycles
}
Common pitfalls
- MAC address randomization: modern iOS and Android periodically rotate their advertised MAC address for privacy, which makes tracking by a fixed MAC unreliable. A more practical fix: use a dedicated BLE device with a fixed MAC (a wristband, a BLE tag like a Nordic Thingy, or an iBeacon) instead of the phone directly, or accept the imperfect accuracy.
- A timeout that’s too short causes false “away” alerts when the phone temporarily stops advertising BLE (screen locked, battery saver) — keep the timeout at least 1-2 minutes.
- Scanning too frequently heats up the ESP32 and wastes power unnecessarily — scanning 5 seconds, resting 5-10 seconds, is plenty for most home use.
- Not handling WiFi/MQTT disconnects leaves the device silently stuck — always check and reconnect, as shown in the sample code.
Extensions
Track multiple devices at once (one MAC per household member) to distinguish “someone’s home” from “who exactly is home,” combine it with a PIR motion sensor for extra confidence, or integrate directly into Home Assistant via ESPHome instead of hand-rolling MQTT firmware.
Tags:#esp32#bluetooth#home-automation
Comments