This update improves the BTPrivacy web dashboard by adding basic hardcoded login protection, logout support, and an embedded favicon served directly by the ESP32 web server.
BTPrivacy
BTPrivacy is an ESP32-S3 Arduino project for BLE privacy noise and local counter-recon.
It fills the nearby BLE scanner view with short-lived, non-connectable entries. The point is simple: when testing your own desk, car, garage, workshop or lab, a scanner should not get a clean list of what is actually nearby.
It does not connect to anything, pair with anything or run a GATT server. It also avoids Apple Continuity, Microsoft Swift Pair and Google Fast Pair style payloads, so it is not a pairing-popup sketch.
Use it only in your own environment. Do not run it in public places or anywhere it could annoy people or interfere with normal devices.
Hardware
Target board:
ESP32-S3 N16R8
16 MB flash
8 MB PSRAM
The project uses ESP32-S3 BLE5 multi-advertising through the Arduino ESP32 Bluedroid stack. A classic ESP32/WROOM does not support this in the same way.
What it does
- runs multiple BLE advertisements at the same time
- lets you change the active slot count from the web UI
- rotates one live advertising slot at a time
- uses valid static-random BLE MAC addresses
- builds a large PSRAM-backed profile pool
- avoids MAC reuse until the generated pool wraps
- uses a larger short-name list so scans look less repetitive
- serves an optional Bootstrap 5 status page over Wi-Fi
- keeps working without Wi-Fi if Wi-Fi is disabled or fails
Default build:
4 live advertising slots
4 maximum configured slots
16384 generated profiles
100 short names
30 manufacturer prefixes
1 slot refresh every 2000 ms
about 8 seconds to refresh the live set
about 9.1 hours before the generated MAC pool wraps
Screenshots
Web dashboard
Phone scanner / visible devices
Arduino IDE setup
Start with this setup:
Arduino IDE
esp32 by Espressif Systems: 3.2.1
Board: ESP32S3 Dev Module
Flash Size: 16MB
PSRAM: OPI PSRAM
Partition Scheme: 16MB Flash / Huge APP or Large APP
USB CDC On Boot: Enabled
USB Mode: Hardware CDC and JTAG, if your menu has it
Upload Speed: 460800 or 921600
Arduino-ESP32 3.2.1 is recommended because this sketch uses Bluedroid BLE5 multi-advertising. Newer cores may push ESP32-S3 BLE sketches toward NimBLE, and NimBLE does not expose the same multi-advertising API here.
No extra BLE library is needed. Do not include NimBLE-Arduino for this sketch.
Uploading
Open:
BTPrivacy/BTPrivacy.ino
If upload hangs at Connecting...:
Hold BOOT
Press and release RST
Keep BOOT held for another second
Release BOOT when upload starts
If it still fails, lower upload speed to 460800 or 115200.
Wi-Fi dashboard
Wi-Fi is optional and disabled by default. There is no AP mode and no fallback hotspot.
To enable the dashboard, edit the Wi-Fi section near the top of BTPrivacyApp.cpp:
static constexpr bool BTPRIVACY_WIFI_ENABLED = true;
static constexpr char BTPRIVACY_WIFI_SSID[] = "YourWiFiName";
static constexpr char BTPRIVACY_WIFI_PASSWORD[] = "YourWiFiPassword";
static constexpr char BTPRIVACY_WIFI_HOSTNAME[] = "btprivacy";
static constexpr bool BTPRIVACY_MDNS_ENABLED = true;
static constexpr uint16_t BTPRIVACY_WEB_PORT = 80;
static constexpr uint32_t BTPRIVACY_WIFI_TIMEOUT_MS = 12000;
static constexpr char BTPRIVACY_WEB_USERNAME[] = "admin";
static constexpr char BTPRIVACY_WEB_PASSWORD[] = "btprivacy";
BTPRIVACY_WIFI_HOSTNAME is the hostname sent to the router. If mDNS works on your network, the dashboard should also be reachable at:
http://btprivacy.local/
The direct IP address is always printed in Serial Monitor when Wi-Fi connects:
Wi-Fi dashboard: http://192.168.x.x/
The dashboard is protected by a local login page. The default credentials are:
Username: admin
Password: btprivacy
To change them, edit BTPRIVACY_WEB_USERNAME and BTPRIVACY_WEB_PASSWORD near the top of BTPrivacyApp.cpp.
The page shows:
- advertising state
- active slot count
- uptime
- rotations
- pool index
- current live slots
- current slot MACs and names
- heap / PSRAM status
- Wi-Fi IP, RSSI and hostname
The page has controls for:
Pause / resume
Rotate faster
Rotate slower
Less slots
More slots
Restart advertisers
Rebuild pool
Changing the slot count restarts the advertising instances cleanly. If the ESP32 BLE controller starts logging errors, use fewer slots.
If Wi-Fi is not configured or the board cannot join the network in time, it shuts Wi-Fi back down and keeps BLE running normally.
The web page uses Bootstrap 5 from a CDN. If the computer/phone opening the dashboard has no internet access, the page still loads, but the styling may look plain.
Serial Monitor
Use:
115200 baud
Tools > USB CDC On Boot > Enabled
Correct port selected after upload
Serial Monitor closed while uploading
The sketch prints a heartbeat every 5 seconds and prints slot refreshes. If you see BLE devices on your phone but Serial is blank, the firmware is running and the problem is only serial routing.
Serial commands:
s status
+ rotate faster
- rotate slower
[ one slot less
] one slot more
p pause / resume advertisements
r rebuild the profile pool
x restart advertisers
Tuning
Most settings are near the top of BTPrivacyApp.cpp.
Good default:
static constexpr uint8_t BTPRIVACY_MIN_ADV_SETS = 1;
static constexpr uint8_t BTPRIVACY_DEFAULT_ADV_SETS = 4;
static constexpr uint8_t BTPRIVACY_MAX_ADV_SETS = 4;
static constexpr uint16_t BTPRIVACY_PROFILE_POOL = 16384;
static constexpr uint32_t BTPRIVACY_DEFAULT_ROTATE_MS = 2000;
If the BLE controller complains, reduce default/max slots first:
static constexpr uint8_t BTPRIVACY_DEFAULT_ADV_SETS = 3;
static constexpr uint8_t BTPRIVACY_MAX_ADV_SETS = 3;
If it is stable and you want faster churn:
static constexpr uint32_t BTPRIVACY_DEFAULT_ROTATE_MS = 1000;
Do not make it too fast. Scanner apps need time to catch the advertisements, and the ESP32 controller needs time to stop and restart a slot cleanly.
Notes
BTPrivacy is a small practical privacy/counter-recon tool for local BLE visibility testing. It creates decoys; it does not connect, pair, exploit or attack.
Keep tests local, keep power reasonable, and do not run it to bother other people.

