How it works
The system has three pieces: the ESP32 (passive Wi-Fi sniffer + BLE GATT server), this maxintel.org page (project documentation), and the web app (Web Bluetooth client with audio metal-detector UI). All audio happens in the browser via Web Audio API — the ESP32 has no buzzer to wire up.
┌─────────────────────────────────────────────────────────────┐
│ ESP32 (classic, BLE 4.2) │
│ ┌──────────────────────┐ ┌──────────────────────────┐ │
│ │ Wi-Fi promiscuous │ │ BLE GATT server │ │
│ │ mode — RX only │ │ • AP-list (notify) │ │
│ │ • Beacon frames │───►│ • Selected BSSID (write)│ │
│ │ • Probe responses │ │ • Live RSSI (notify) │ │
│ │ • Channel hop 1-13 │ │ • Status (notify) │ │
│ └──────────────────────┘ └──────────────┬───────────┘ │
└────────────────────────────────────────────┼───────────────┘
│ BLE 4.2
┌──────────────▼───────────┐
│ Browser (Web Bluetooth) │
│ • AP picker UI │
│ • Live RSSI dashboard │
│ • Web Audio beeper │
│ • navigator.vibrate() │
└──────────────────────────┘
Signal flow when you walk toward an AP
- ESP32 captures a beacon frame from your selected target AP. RSSI is read from
pkt->rx_ctrl.rssi, smoothed via exponential moving average (α=0.30 — fast enough to feel responsive, slow enough to kill jitter).
- Smoothed RSSI is pushed over BLE notification at ~10 Hz to the browser.
- Browser receives the int8 dBm value, updates the visual meter, computes a beep gap from a quadratic curve (gap = 2000 − 1940·t² ms, where t is normalized strength).
- Web Audio fires a square-wave tone burst whose pitch also rises with strength (600Hz at floor → 1800Hz near AP) for an extra perceptual cue.
- Optional
navigator.vibrate() pulse on phones for haptic feedback.
Why receive-only matters (legally and technically)
The firmware runs the radio in WIFI_MODE_NULL + promiscuous mode with WIFI_PROMIS_FILTER_MASK_MGMT. The ESP32 never transmits an 802.11 frame. No probe requests, no deauths, no authentication attempts. Sending crafted management frames against networks you don't own is illegal in many jurisdictions (FCC §47 CFR 333 in the US, equivalent regulations elsewhere); a passive RSSI direction-finder is in a different legal category entirely.
Beacons and probe responses are broadcast publicly by every AP — they're what your phone uses to populate its Wi-Fi list. Listening to them is no different from looking at the SSIDs your laptop shows you. Hidden APs (SSID broadcast suppressed) still beacon — they just send empty SSID fields — so they're findable too, by BSSID and channel.
Hardware setup
| Item | Notes |
| ESP32 dev board | Classic ESP32 (DOIT DevKit V1, NodeMCU-32S, etc). ~$5–10. The build also runs on ESP32-S3 with minor changes; ESP32-C3 needs slightly different partition table. |
| USB cable | Micro-USB or USB-C depending on board, for flashing and power. |
| Battery (optional) | Any 5V power bank. Once flashed, the board only needs power — all UI happens on your phone/laptop. A small LiPo + TP4056 charger module makes a portable build for ~$2 extra. |
| Antenna (optional) | Most ESP32 modules have an onboard PCB antenna. Boards with U.FL connectors (look for "ESP32-WROOM with IPEX") let you attach a directional 2.4GHz Yagi or patch antenna for actual direction-finding (not just proximity). |
No buzzer required. All audio is generated in the browser via Web Audio API. The ESP32 only blinks the onboard LED for status — no extra components to solder.
Why classic ESP32 over S3 or C3?
Classic ESP32 has BLE 4.2 (Bluetooth Classic + LE). The S3 has BLE 5 with longer range and faster data, and the C3 is RISC-V and cheaper. All three work for this project. The classic ESP32 was chosen as the default target because it's the most widely available, cheapest, and best-documented chip; if you have an S3 or C3, change board = esp32dev to esp32-s3-devkitc-1 or esp32-c3-devkitm-1 in platformio.ini and the same code compiles.
Legal & ethical use
This tool is a passive RSSI direction-finder. It never transmits 802.11 frames at any AP. But locating someone else's hidden network without permission may still violate computer-misuse laws in your jurisdiction (CFAA in the US, Computer Misuse Act in the UK, similar laws elsewhere). The legitimate uses are well-defined:
- Finding your own equipment. Lost an AP, a smart-home gateway, or a Wi-Fi-enabled device on your own property? Lock to its BSSID and walk it down.
- Locating rogue devices on networks you administer. Hidden Wi-Fi cameras in rentals, rogue APs on your corporate network, devices you didn't install. Authorization to be on the property + authorization to audit is the bar.
- Authorized penetration tests. If you have a written scope of work that authorizes wireless reconnaissance, this is exactly the tool category covered by it.
- Educational / security research. Understanding 802.11 management frames, RSSI propagation, BLE GATT — pointed at your own test APs.
Hard line: do not point this at networks or devices you don't own or have written authorization to audit. The tool architecture (passive RX-only, no frame injection) keeps you on the right side of FCC §333 and equivalents, but doesn't override jurisdiction-specific computer-misuse and stalking statutes. Use is your responsibility.
AP-Finder: ESP32 + Web Bluetooth Wi-Fi direction finder
Open-source hardware/software project. ESP32 dev board passively sniffs nearby 2.4GHz access points, exposes them over BLE GATT, and the browser turns RSSI into a metal-detector audio signal. Built on PlatformIO + NimBLE-Arduino + Web Bluetooth + Web Audio API.
Designed for finding your own Wi-Fi equipment, locating rogue or hidden devices on your own property, and authorized network audits. Receive-only architecture — the firmware never transmits 802.11 frames at any AP. No buzzer required; all audio happens in the browser via Web Audio.
Stack: ESP32 classic (also works on S3/C3), PlatformIO, NimBLE-Arduino, ESP-IDF promiscuous WiFi API, Web Bluetooth, Web Audio API. Browser support: Chrome/Edge/Brave on Android, macOS, Windows, Linux. iOS via the Bluefy browser app. Firefox not supported.
Frequently asked questions
Does this tool send any data to access points?
No. The ESP32 firmware runs the radio in WIFI_MODE_NULL with promiscuous-mode RX enabled. It captures beacons and probe responses that are already broadcast publicly. It never transmits an 802.11 frame at any AP — no probe requests, no deauths, no authentication attempts. The only RF the device emits is the BLE advertisement to the controlling browser. This keeps the design clearly within FCC §47 CFR 333 (no willful interference) and equivalent regulations elsewhere.
Can it find hidden Wi-Fi networks?
Yes. Hidden APs (SSID broadcast suppressed) still emit beacon frames — they just send a zero-length or all-zeros SSID field. The firmware captures them by BSSID and marks them as hidden: true in the AP list. The web app shows them as "⟨ hidden network ⟩". You can lock onto a hidden AP just like a named one and direction-find by RSSI.
How accurate is the direction-finding?
It's a proximity tool, not a true bearing-finder. With a stock ESP32 and its onboard PCB antenna (omnidirectional in the chip plane), RSSI tells you "stronger / weaker" but not which direction the AP is in. To turn it into actual direction-finding, attach a directional 2.4GHz antenna (small Yagi or patch) to a U.FL-equipped ESP32 module. RSSI then drops sharply when you face away from the target, giving you a real bearing.
Why classic ESP32 instead of S3 or C3?
Classic ESP32 was chosen as the default because it's the cheapest, most widely available, and best-documented chip in the family. The same code runs on ESP32-S3 (BLE 5, longer range, USB-native) and ESP32-C3 (RISC-V, cheaper) with one line changed in platformio.ini. If you're buying new, S3 is arguably the better pick; if you have a classic ESP32 in a drawer, it works fine.
Why is the audio in the browser instead of a buzzer?
Three reasons. (1) Free pitch and cadence control — the browser can rise the tone with strength, vary the envelope, and even add haptic vibration on phones, all without re-flashing. (2) No extra hardware — no buzzer, transistor, or current-limit resistor to wire up. (3) Lower battery drain on the ESP32 — every component you don't add is current you don't draw. Web Audio API is mature and works in every browser that supports Web Bluetooth.
Will it work on iPhone?
Not directly through Safari — Apple has not implemented Web Bluetooth API in Safari and there's no signal they will. The workaround is Bluefy, a free Safari-shell browser that adds Web Bluetooth on iOS; install it from the App Store and open the web app inside Bluefy. On Android, Chrome works natively.
Does it work simultaneously with WiFi promiscuous mode?
Yes, with NimBLE. The classic ESP32 has one shared 2.4GHz radio that time-divides between Bluetooth and WiFi via firmware coexistence (CONFIG_SW_COEXIST_ENABLE=1). With NimBLE-Arduino (instead of the bulky default Bluedroid stack), the combined memory footprint fits within ESP32 RAM and packet loss on either side is minimal. The build configures this automatically in platformio.ini.
How does CSI fit into this?
It doesn't — and that's intentional. The original idea was "send packets using CSI", but Channel State Information is metadata extracted from frames you receive, not something you transmit. For direction-finding, plain RSSI is a better signal anyway: it's simpler, more stable, runs in pure receive mode (no transmissions), and matches the metal-detector UX cleanly. CSI is useful for human-presence and motion detection — a different feature that could be added later without changing the wireless behavior.
Why ~10 Hz RSSI updates and not faster?
BLE notification rate caps and stack overhead. ESP32 BLE 4.2 stack reliably handles 10–20 notifications per second per characteristic; pushing higher causes occasional drops and adds CPU load. 10 Hz is well-matched to human reaction time for the metal-detector UX — at 100ms intervals you get smooth perceived feedback without wasting radio time. The browser smooths over jitter visually with CSS transitions.
Can I export the AP list / RSSI samples?
Not in v1, but it's a small addition. The browser already has the data in aps Map and lastRssi live samples — adding an "Export to CSV" button is ~20 lines. Open an issue or fork the project. (For wardriving-style logging with GPS, you want a different toolchain — WiGLE WiFi Wardriving on Android is the canonical option and is a separate category from this single-target direction-finder.)
What about ESP32 with external Wi-Fi adapters or 5GHz?
Classic ESP32 is 2.4GHz only — that's baked into the silicon. ESP32-C5 and the Wi-Fi 6 chips (ESP32-C6) add 5GHz / 6GHz support and the same firmware approach works there with promiscuous-mode API parity. For now, if your target AP is 5GHz-only, this build won't see it; switch to a C5/C6 board.
Where do I get help?
Open an issue on the project source (linked at the top of this page). Common gotchas: esp_wifi_set_promiscuous failing if you didn't call esp_wifi_set_mode(WIFI_MODE_NULL) first; the AP list looking empty if the channel hopper hasn't completed its first 1–13 sweep (~3 seconds); web app showing "Web Bluetooth not available" on Firefox or iOS Safari (use Chrome or Bluefy instead).