When makers and penetration testers search for an esp32-bluejammer, they are usually looking for a Bluetooth Low Energy (BLE) advertisement protocol flooder. Let's clear up a critical distinction right out of the gate: a true RF jammer transmits wideband noise across the 2.4GHz spectrum to physically block signals. Building or operating a physical RF jammer is a federal offense under FCC Part 15 regulations and similar laws globally.
However, a protocol flooder (often colloquially called a bluejammer or BLE spammer) operates entirely within the legal BLE protocol stack. It rapidly broadcasts valid, randomized BLE advertising packets to test how local scanners, smartphones, and IoT gateways handle high-volume payload ingestion. This guide walks you through building a bench-top BLE protocol flooder using the NimBLE stack to test device resilience, complete with exact wiring, compilable code, and the specific error traps you will hit along the way.
Hardware BOM and Spec Sheet
To handle the high-throughput memory demands of rapidly cycling BLE advertisements without crashing the RTOS, we are bypassing the default Bluedroid stack in favor of NimBLE. This requires a board with adequate PSRAM or a well-managed heap. The standard ESP32-WROOM-32E is sufficient if we manage the partition scheme correctly.
| Component | Exact Model / Variant | Purpose | Approx Cost |
|---|---|---|---|
| Microcontroller | ESP32 DevKit V1 (30-pin, ESP32-WROOM-32E) | Main BLE TX and logic controller | $6.50 |
| Display | 0.96" I2C OLED (SSD1306 driver, 0x3C address) | Real-time packet count and MAC display | $4.00 |
| Power Supply | 18650 Li-ion Battery Shield (Micro-USB charge) | Portable 5V/3.3V power regulation | $3.50 |
| Wiring | 24 AWG silicone jumper wires (male-to-female) | I2C and power bus connections | $5.00 |
Pin Mapping and Assembly Steps
The assembly is straightforward. We are using the hardware I2C bus for the OLED to keep CPU overhead low while the radio handles the BLE stack.
| OLED Pin (SSD1306) | ESP32 DevKit V1 Pin | Function |
|---|---|---|
| VCC | 3V3 | Logic Power (Do not use 5V) |
| GND | GND | Common Ground |
| SCL | GPIO 22 | I2C Clock |
| SDA | GPIO 21 | I2C Data |
- Prep the Power Shield: Solder the female header pins to the 18650 battery shield. Insert a protected 18650 cell (e.g., Panasonic NCR18650B) ensuring correct polarity.
- Mount the ESP32: Plug the ESP32 DevKit V1 into a half-size breadboard, straddling the center trench.
- Wire the I2C Bus: Connect GPIO 21 to SDA and GPIO 22 to SCL. Keep these wires under 3 inches to prevent I2C capacitance issues.
- Power Injection: Route the 5V out from the battery shield to the ESP32's
VINpin (not 3V3, as the onboard AMS1117 regulator needs the overhead to power the radio TX bursts).
Complete Compilable BLE Flooder Code
This code targets the ESP32 DevKit V1 (ESP32-WROOM-32E). You must install the NimBLE-Arduino and Adafruit SSD1306 libraries via the Arduino Library Manager. The code generates randomized local names and payloads to prevent scanner MAC-filtering from ignoring the packets.
#include <NimBLEDevice.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// --- Pin & Hardware Definitions ---
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C // Verify with I2C scanner if 0x3D
// --- Global Variables ---
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
unsigned long packetCount = 0;
char payloadBuffer[28];
void setup() {
Serial.begin(115200);
// Initialize Display with Error Handling
if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed. Check I2C address and wiring."));
while(true) { delay(100); } // Halt execution
}
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0,0);
display.println("BLE Flooder Init...");
display.display();
// Initialize NimBLE Stack
NimBLEDevice::init("FluxTester");
// Lower TX power to reduce heat and battery drain during bench testing
NimBLEDevice::setPower(ESP_PWR_LVL_N12);
display.setCursor(0,10);
display.println("Stack Ready.");
display.display();
delay(1000);
}
void loop() {
NimBLEAdvertising *pAdvertising = NimBLEDevice::getAdvertising();
// Generate Randomized Payload to bypass scanner deduplication
String randName = "Test_" + String(random(1000, 9999));
NimBLEAdvertisementData oAdvertisementData = NimBLEAdvertisementData();
oAdvertisementData.setName(randName.c_str());
// Fill remaining bytes with random data
for(int i=0; i<16; i++) {
payloadBuffer[i] = (char)random(0x20, 0x7E);
}
oAdvertisementData.addData(std::string(payloadBuffer, 16));
pAdvertising->setAdvertisementData(oAdvertisementData);
// Start and stop advertising rapidly
pAdvertising->start();
delay(50); // 50ms burst window
pAdvertising->stop();
packetCount++;
updateDisplay(randName);
}
void updateDisplay(String currentName) {
display.clearDisplay();
display.setCursor(0,0);
display.println("ESP32 BLE Flooder");
display.drawLine(0, 10, 128, 10, SSD1306_WHITE);
display.setCursor(0, 15);
display.print("Packets: ");
display.println(packetCount);
display.setCursor(0, 25);
display.print("Target: ");
display.println(currentName);
display.setCursor(0, 35);
display.print("TX Pwr: -12dBm");
display.display();
}
Debugging: First Three Checks and Common Errors
When working with high-speed BLE stack manipulation on the ESP32, the RTOS will panic if memory or timing boundaries are crossed. If your build fails or reboots endlessly, run through these checks.
The First Three Things to Check
- Partition Scheme: The default app partition is too small for NimBLE + Adafruit GFX. In the Arduino IDE Tools menu, change Partition Scheme to Huge APP (3MB No OTA/1MB SPIFFS).
- Board Definition Mismatch: Ensure you have selected DOIT ESP32 DEVKIT V1 or ESP32 Dev Module. Selecting an ESP32-S3 or C3 variant will compile but fail at runtime due to different I2C and radio routing.
- I2C Address Conflict: If the serial monitor outputs
SSD1306 allocation failed, your OLED is likely using address0x3D. Run a basic I2C scanner sketch to confirm, then update theSCREEN_ADDRESSmacro.
Exact Error String: bta_dm_ble_set_adv_data, error 0x104
If you modify the payload generator and see this in your serial monitor:
E (5432) BT_BLE: bta_dm_ble_set_adv_data, error 0x104
Ranked Causes:
- Payload Exceeds 31 Bytes: BLE 4.2 advertising packets have a strict 31-byte limit for the payload. If your random string plus the device name exceeds this, the HCI layer rejects it with 0x104 (Invalid Parameters). Fix: Truncate your strings to ensure the total advertised data remains under 28 bytes (leaving room for headers).
- Simultaneous Scan and Advertise: If you added scanning logic to the loop, the BLE controller cannot simultaneously set advertising data while scanning on the same PHY. Fix: Stop scanning before calling setAdvertisementData().
Exact Error String: Guru Meditation Error (Interrupt wdt timeout)
Guru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout on CPU1)
Cause: You replaced the delay(50) in the loop with a blocking while() loop or a delayMicroseconds() that starves the FreeRTOS idle task, preventing the watchdog from being fed. Fix: Always use standard delay() or vTaskDelay() to yield to the RTOS radio tasks.
Extending and Simplifying the Build
Depending on your testing environment, you may want to strip this down or scale it up.
How to Simplify (Headless Mode):
If you are doing automated bench testing where you log data via a PC, drop the SSD1306 display entirely. Remove the Wire and Adafruit includes, delete the display update function, and output the packet count to Serial in CSV format. This frees up roughly 15KB of flash and eliminates I2C bus contention, allowing you to drop the delay window down to 20ms.
How to Extend (Multi-Protocol Targeting):
To test specific vendor ecosystems (like Apple's continuity protocol or Samsung's Fast Pair), you need to inject specific manufacturer data headers. Add a physical pushbutton wired to GPIO 15 (with a 10k pull-down). Read the button state in the loop to cycle through an array of predefined NimBLEAdvertisementData structures that contain the exact hex bytes for vendor-specific beacons. You can reference the Espressif NimBLE API documentation for the setManufacturerData() method to implement this.
FAQ: ESP32 BlueJammer and BLE Flooding Questions
Can an ESP32 bluejammer block Bluetooth audio or Wi-Fi?
No. This build is a protocol-level flooder, not a physical-layer RF jammer. It sends valid BLE advertising packets that confuse the software scanners on smartphones and IoT hubs by overwhelming their UI or parsing queues. It does not transmit wideband noise, meaning your 2.4GHz Wi-Fi, Bluetooth audio streaming, and microwave oven will continue to operate normally at the physical layer. True RF jamming requires specialized, illegal hardware that drowns out the noise floor.
Why does my ESP32 overheat when running the BLE flooder continuously?
The ESP32-WROOM-32E integrates a 2.4GHz radio and a dual-core 240MHz CPU on a single silicon die. Rapidly starting and stopping the BLE advertising stack forces the CPU to constantly allocate and deallocate memory for the HCI layer, while the radio draws peak current (up to 130mA) during TX bursts. To mitigate thermal throttling, the code above sets the TX power to ESP_PWR_LVL_N12 (-12dBm). If you are still experiencing heat issues, increase the delay between bursts from 50ms to 150ms, or attach a small 15x15x4mm aluminum heatsink to the metal RF shield.
Is it legal to carry a BLE protocol flooder in public?
Owning and building a BLE protocol tester is legal in most jurisdictions, as it operates within the standard BLE specifications defined by the Bluetooth SIG. However, using it to intentionally disrupt commerce, trigger false emergency alerts on public transit, or deny service to public infrastructure falls under computer fraud and abuse laws. Always restrict testing to your own lab environment, your own devices, or authorized penetration testing engagements where you have explicit written consent from the network owner.






