-**Standalone BLE surveillance device detector with web dashboard, GPS wardriving, and session persistence.**
+**Passive 2.4 GHz promiscuous-mode detector for Flock Safety surveillance infrastructure. Runs standalone or feeds the Flask dashboard over USB for live GPS-tagged wardriving.**
-Available as part of the OUI-SPY project at [colonelpanic.tech](https://colonelpanic.tech)
+> **Dev note:** This is the `promiscious-dev` branch — adds the
+> DeFlockJoplin wildcard-probe tightening and a 31st OUI on top of the
+> `promiscious` baseline. See "Further research" below.
---
-## Overview
+## Credit
-Flock-You detects Flock Safety surveillance cameras, Raven gunshot detectors, and related monitoring hardware using BLE-only heuristics. It runs a WiFi access point with a live web dashboard on your phone, tags detections with GPS from your phone's browser, and exports everything as JSON, CSV, or KML for Google Earth.
+All WiFi promiscuous detection research — the **30-OUI target list**, the **promiscuous-mode strategy**, and the **addr1-receiver detection technique** — is the work of **ØяĐöØцяöЪöяцฐ / @NitekryDPaul**. The firmware here is a mod of his original firmware with added SPIFFS persistence and Flask-dashboard integration. Full research writeup: [`datasets/NitekryDPaul_wifi_ouis.md`](datasets/NitekryDPaul_wifi_ouis.md).
-No WiFi sniffing — the radio is dedicated to serving the dashboard AP while BLE scans continuously in the background via ESP32 coexistence.
+Additional research credit to **Michael / DeFlockJoplin** for the **wildcard-probe-request signature** and the 31st OUI (`82:6b:f2`). Field-tested to 11/12 cameras caught with only 2 false positives in Joplin. Source: [DeflockJoplin/flock-you](https://github.com/DeflockJoplin/flock-you).
---
-## Detection Methods
+## What this branch does
-All detection is BLE-based:
+Turns a Seeed XIAO ESP32-S3 into a passive WiFi receiver that watches 2.4 GHz management and data frames for Flock Safety MAC OUIs. No AP, no transmit — the radio stays dedicated to sniffing while the device hops channels 1 / 6 / 11 at 350 ms dwell.
-| Method | Description |
-|--------|-------------|
-| **MAC prefix** | 20 known Flock Safety OUI prefixes (FS Ext Battery, Flock WiFi modules) |
-| **BLE device name** | Case-insensitive substring match: `FS Ext Battery`, `Penguin`, `Flock`, `Pigvision` |
-| **Manufacturer ID** | `0x09C8` (XUNTONG) — catches devices with no broadcast name. *From [wgreenberg/flock-you](https://github.com/wgreenberg/flock-you)* |
-| **Raven service UUID** | Identifies Raven gunshot detectors by BLE GATT service UUIDs |
-| **Raven FW estimation** | Determines firmware version (1.1.x / 1.2.x / 1.3.x) from advertised service patterns |
+Every detection is:
+
+- beeped (piezo on GPIO3) and flashed (onboard LED on GPIO21)
+- written to on-device SPIFFS in an atomic CRC-envelope format, surviving power loss
+- emitted as one JSON line over USB CDC in the schema `api/flockyou.py` expects, so the Flask dashboard auto-ingests it with GPS temporal matching
+
+The device works standalone (no USB host needed) and plugged in (live dashboard) without any mode switch.
---
-## Features
+## Why promiscuous mode, and why `addr1`
-- **WiFi AP**: `flockyou` / password `flockyou123`
-- **Web dashboard** at `192.168.4.1` — live detection feed, pattern database, export tools
-- **GPS wardriving** — phone GPS via browser Geolocation API tags every detection with coordinates
-- **Session persistence** — detections auto-save to flash (SPIFFS) every 60 seconds
-- **Prior session tab** — previous session survives reboot and is viewable in the PREV tab
-- **Export formats**: JSON, CSV, and KML (Google Earth) — current and prior sessions
-- **Serial output** — Flask-compatible JSON over serial for live desktop ingestion
-- **200 unique device storage** with FreeRTOS mutex thread safety
-- **Crow call boot sounds** — modulated descending frequency sweeps with warble texture
-- **Detection alerts** — ascending chirps + descending caw on new device detection
-- **Heartbeat** — soft double coo every 10s while a device stays in range
+Most WiFi sniffers only check the transmitter address (`addr2`). Flock infrastructure spends most of its duty cycle **asleep** — it wakes briefly in bursts, uploads, then sleeps again. During the silence it may never transmit a single frame in your capture window.
+
+But it may still appear on the air as the **destination** (`addr1`) of probe responses or data frames from nearby APs.
+
+Checking `addr1` in addition to `addr2` picks those silent stations up. It requires two guards to avoid false positives:
+
+- `addr1` is broadcast (`ff:ff:ff:ff:ff:ff`) in beacons and broadcasts — **multicast filter**
+- Modern devices use randomised (locally-administered) MACs that can't be fingerprinted by OUI — **randomised-MAC filter** on byte 0 bit 1
+
+Both are applied before the OUI match. This whole approach, including the 30-OUI list, is **@NitekryDPaul's research**.
---
-## Enabling GPS (Android Chrome)
+## Further research — the wildcard-probe signature (DeFlockJoplin)
-The dashboard uses your phone's GPS to geotag detections. Because it's served over HTTP, Chrome requires a one-time flag change:
+Michael / DeFlockJoplin used the OUI + addr1/addr2/addr3 work above as a starting point and characterised what Flock cameras actually do on the air. His finding:
-1. Open a new Chrome tab and go to `chrome://flags`
-2. Search for **"Insecure origins treated as secure"**
-3. Add `http://192.168.4.1` to the text field
-4. Set the flag to **Enabled**
-5. Tap **Relaunch**
+> The cameras are hopping channels and sending out a wildcard WiFi probe request on every channel. This specific type of request combined with OUI matching has created what seems to be a fairly unique signature.
-After relaunching, connect to the `flockyou` AP, open `192.168.4.1`, and tap the **GPS** card in the stats bar to grant location permission.
+His drive-test in Joplin caught **11 of 12 cameras** with only **2 false positives**. The 12th camera was doing the same wildcard-probe behaviour but with an OUI (`82:6b:f2`) that wasn't in @NitekryDPaul's original 30 — it's now the 31st entry in our list, credited to him.
-> **Note:** iOS Safari does not support Geolocation over HTTP. GPS wardriving requires Android with Chrome.
+The tightened signature that's active on this branch:
+
+1. Frame is 802.11 Management, type=0 subtype=4 (**Probe Request**)
+2. SSID Information Element (tag 0) is present with **length 0** (wildcard)
+3. `addr2` (transmitter) matches the known-OUI list
+
+When all three hit, we emit `detection_method: wifi_wildcard_probe` — the high-precision class. Non-probe frames from the same OUIs still emit `wifi_oui_addr2`, and the `addr1` receiver-side sleeper-catch still runs independently.
+
+His proof-of-concept firmware (different enough we're not just pulling it in wholesale, but the core idea carried over cleanly): [DeflockJoplin/flock-you](https://github.com/DeflockJoplin/flock-you). The wildcard-probe analysis is his; we ported the detection into this firmware and kept our SPIFFS persistence, Flask JSON emission, and audio/LED feedback on top.
+
+---
+
+## Detection pipeline
+
+```
+ [2.4GHz air]
+ │
+ ▼
+ wifiSniffer() ← IRAM promiscuous callback (WiFi task)
+ │ fast match only, no Serial / no malloc
+ ▼
+ alertQueue[32] ← lock-free ring buffer (ISR-safe mux)
+ │
+ ▼
+ drainAlertQueue() ← loop() context, per-iteration drain
+ │
+ ├─► fyAddDetection() ← always, every hit
+ │ │
+ │ ▼
+ │ fyDet[200] ← unique-by-MAC on-device table
+ │ │
+ │ ▼
+ │ autosaveTick() ← every 60s when dirty
+ │ │
+ │ ▼
+ │ fySaveSession() ← atomic CRC-envelope write to SPIFFS
+ │
+ ├─► shouldSuppressDuplicate() ← 5s per-MAC serial-emit rate limit
+ │
+ └─► emitDetectionJSON() ← USB CDC line for Flask
+ buzzerBeep() + ledFlash()
+```
+
+The split between callback and loop is deliberate: the WiFi task has hard real-time constraints and cannot call `Serial.print` or `malloc` safely. The callback writes only to the lock-free ring buffer; `loop()` does all the heavy work.
+
+---
+
+## OUI target list (@NitekryDPaul research)
+
+All lowercase, colon-separated. 31 Flock Safety infrastructure prefixes:
+
+```
+70:c9:4e 3c:91:80 d8:f3:bc 80:30:49 b8:35:32
+14:5a:fc 74:4c:a1 08:3a:88 9c:2f:9d c0:35:32
+94:08:53 e4:aa:ea f4:6a:dd f8:a2:d6 24:b2:b9
+00:f4:8d d0:39:57 e8:d0:fc e0:4f:43 b8:1e:a4
+70:08:94 58:8e:81 ec:1b:bd 3c:71:bf 58:00:e3
+90:35:ea 5c:93:a2 64:6e:69 48:27:ea a4:cf:12
+82:6b:f2 ← contributed by Michael / DeFlockJoplin
+```
+
+Pre-compiled into a byte table in `setup()` so the matcher stays entirely in IRAM with no flash-resident lookups during callback execution.
+
+Full dataset and methodology: [`datasets/NitekryDPaul_wifi_ouis.md`](datasets/NitekryDPaul_wifi_ouis.md).
+
+---
+
+## SPIFFS wire format
+
+On-flash layout, atomic and crash-safe:
+
+```
+Line 1: {"v":1,"count":N,"bytes":B,"crc":"0xXXXXXXXX"}
+Line 2: [{"mac":"...","method":"...","rssi":...,...},...]
+```
+
+Save procedure:
+
+1. Compute CRC32 + byte count over the serialised payload
+2. Write envelope header + payload to `/session.tmp`
+3. Re-read and re-validate `/session.tmp` (CRC check)
+4. Remove `/session.json`
+5. Atomic rename `/session.tmp` → `/session.json` (copy+delete fallback)
+
+Boot recovery:
+
+1. If `/session.json` validates, promote it to `/prev_session.json`
+2. Otherwise try `/session.tmp` (interrupted save)
+3. Delete both working files, start with an empty live table
+4. `/prev_session.json` stays around for inspection
+
+CRC32 uses the standard `0xEDB88320` polynomial so the same file can be verified on a host with any off-the-shelf CRC tool.
+
+---
+
+## Flask dashboard integration
+
+The firmware emits one JSON line per detection in the same schema the BLE detector uses, so `api/flockyou.py` picks it up with zero changes:
+
+```json
+{"event":"detection","detection_method":"wifi_oui_addr2","protocol":"wifi_2_4ghz","mac_address":"aa:bb:cc:dd:ee:ff","oui":"aa:bb:cc","device_name":"","rssi":-62,"channel":6,"frequency":2437,"ssid":""}
+```
+
+`detection_method` values:
+
+- `wifi_wildcard_probe` — **Probe Request + wildcard SSID from a known OUI** (the DeFlockJoplin high-precision signature). When this fires, the `addr2` broad alert is suppressed for the same frame to avoid double-counting.
+- `wifi_oui_addr2` — transmitter-side OUI match on any non-probe frame
+- `wifi_oui_addr1` — **receiver-side OUI match** (the @NitekryDPaul technique)
+- `wifi_oui_addr3` — BSSID OUI match (mgmt frames only; disabled by default)
+- `wifi_ssid` — SSID keyword match (disabled by default)
+
+### GPS wardriving
+
+GPS is handled Flask-side, since the ESP32 radio is dedicated to sniffing and there's no on-device AP. Two options:
+
+- **USB NMEA puck** plugged into the host running Flask — Flask reads NMEA and timestamps a GPS timeline
+- **Flask dashboard open in a phone browser** — browser Geolocation API posts updates to Flask
+
+Flask does a temporal match between detection timestamp and GPS timeline, then exports JSON / CSV / KML for Google Earth.
+
+### Running Flask
+
+```bash
+cd api
+pip install -r requirements.txt
+python flockyou.py
+```
+
+Open `http://localhost:5000`, pick your serial port from the UI, detections start showing up live.
---
@@ -69,70 +194,70 @@ After relaunching, connect to the `flockyou` AP, open `192.168.4.1`, and tap the
| Pin | Function |
|-----|----------|
| GPIO 3 | Piezo buzzer |
-| GPIO 21 | LED (optional) |
+| GPIO 21 | Onboard user LED (active low) |
+| GPIO 43 | Serial1 TX mirror (115200 baud) |
+
+Boot sound: first 6 notes of Super Mario Bros. World 1-2 (underground).
---
-## Building & Flashing
+## Build and flash
Requires [PlatformIO](https://platformio.org/).
```bash
-cd flock-you
pio run # build
pio run -t upload # flash
pio device monitor # serial output
```
-**Dependencies** (managed by PlatformIO):
-
-- `NimBLE-Arduino` — BLE scanning
-- `ESP Async WebServer` + `AsyncTCP` — web dashboard
-- `ArduinoJson` — JSON serialization
-- `SPIFFS` — session persistence to flash
+`platformio.ini` and `partitions.csv` are at the root (1.9 MB SPIFFS partition, 6 MB app). No extra libraries needed beyond the Arduino-ESP32 core that ships with the espressif32 platform.
---
-## Flask Companion App
+## Config cheatsheet (top of `main.cpp`)
-The `api/` folder contains a Flask web application for desktop analysis of detection data.
-
-```bash
-cd api
-pip install -r requirements.txt
-python flockyou.py
-```
-
-Open `http://localhost:5000` for the desktop dashboard.
-
-**Import support:** JSON, CSV, and KML files exported from the ESP32 can be imported directly into the Flask app. Live serial ingestion is also supported — connect the ESP32 via USB and select the serial port in the Flask UI.
+| Define | Default | Notes |
+|---|---|---|
+| `CHANNEL_MODE` | `CHANNEL_MODE_CUSTOM` | `CUSTOM` (1/6/11), `FULL_HOP` (1-11), or `SINGLE` |
+| `CHANNEL_DWELL_MS` | 350 | Time on each channel before hop |
+| `RSSI_MIN` | -95 | Drop frames weaker than this |
+| `ALERT_COOLDOWN_MS` | 5000 | Per-MAC serial-emit rate limit |
+| `CHECK_ADDR1` | 1 | The @NitekryDPaul receiver-side technique |
+| `CHECK_ADDR3` | 0 | BSSID fallback (mgmt frames only) |
+| `ENABLE_SSID_MATCH` | 0 | Substring match against `target_ssid_keywords[]` |
+| `PROCESS_MGMT_FRAMES` | 1 | Beacons, probe req/resp, etc. |
+| `PROCESS_DATA_FRAMES` | 1 | Data frames (where addr1 catch shines) |
+| `MAX_DETECTIONS` | 200 | On-device table cap |
+| `AUTOSAVE_INTERVAL_MS` | 60000 | SPIFFS save cadence |
+| `LED_PIN` | 21 | Onboard user LED |
+| `BUZZER_PIN` | 3 | Piezo |
---
-## Raven Gunshot Detector Detection
+## Standalone vs connected
-Flock-You identifies SoundThinking/ShotSpotter Raven devices through BLE service UUID fingerprinting:
+**Without USB:** device boots, plays the SMB 1-2 intro, starts scanning, stores every unique detection to SPIFFS, flashes the onboard LED on each hit. Plug in later — the prior session is sitting in `/prev_session.json`.
-| Service | UUID | Description |
-|---------|------|-------------|
-| Device Info | `0000180a-...` | Serial, model, firmware |
-| GPS | `00003100-...` | Real-time coordinates |
-| Power | `00003200-...` | Battery & solar status |
-| Network | `00003300-...` | LTE/WiFi connectivity |
-| Upload | `00003400-...` | Data transmission metrics |
-| Error | `00003500-...` | Diagnostics & error logs |
-| Health (legacy) | `00001809-...` | Firmware 1.1.x |
-| Location (legacy) | `00001819-...` | Firmware 1.1.x |
+**With USB + Flask running:** same thing, plus every detection streams live to the dashboard as a JSON line. Flask adds GPS (if configured) and deduplicates across MAC, building the wardriving map as you move.
-Firmware version is estimated automatically from which service UUIDs are advertised.
+Both modes work simultaneously — the SPIFFS write path doesn't care if a host is listening.
+
+---
+
+## BLE companion firmware
+
+The BLE-only sibling of this firmware lives on the [`main` branch](https://github.com/colonelpanichacks/flock-you/tree/main). It detects Flock and Raven gear via BLE advertisements (OUI prefix, device name, manufacturer ID `0x09C8`, Raven service UUIDs), runs its own WiFi AP with a phone-facing dashboard at `192.168.4.1`, and emits the same Flask JSON schema. Flash both on separate boards for overlapping BLE + WiFi coverage feeding one Flask dashboard.
---
## Acknowledgments
-- **Will Greenberg** ([@wgreenberg](https://github.com/wgreenberg)) — BLE manufacturer company ID detection (`0x09C8` XUNTONG) sourced from his [flock-you](https://github.com/wgreenberg/flock-you) fork
+- **ØяĐöØцяöЪöяцฐ (@NitekryDPaul)** — **WiFi promiscuous detection research**: the 30-OUI Flock Safety target list and the addr1-receiver detection technique that are the baseline of this firmware. The code here is a mod of his original work.
+- **Michael / DeFlockJoplin** ([DeflockJoplin/flock-you](https://github.com/DeflockJoplin/flock-you), [deflockjoplin.today](https://deflockjoplin.today)) — **wildcard-probe-request signature** + the 31st OUI (`82:6b:f2`). Drive-tested in Joplin to 11/12 cameras caught with only 2 false positives.
+- **Will Greenberg** ([@wgreenberg](https://github.com/wgreenberg)) — BLE manufacturer company ID detection (`0x09C8` XUNTONG) sourced from his [flock-you](https://github.com/wgreenberg/flock-you) fork (used by the BLE companion on `main`)
- **[DeFlock](https://deflock.me)** ([FoggedLens/deflock](https://github.com/FoggedLens/deflock)) — crowdsourced ALPR location data and detection methodologies. Datasets included in `datasets/`
-- **[GainSec](https://github.com/GainSec)** — Raven BLE service UUID dataset (`raven_configurations.json`) enabling detection of SoundThinking/ShotSpotter acoustic surveillance devices
+- **[GainSec](https://github.com/GainSec)** — Raven BLE service UUID dataset (`raven_configurations.json`) used by the BLE companion
---
@@ -162,4 +287,4 @@ Flock-You is part of the OUI-SPY firmware family:
## Disclaimer
-This tool is intended for security research, privacy auditing, and educational purposes. Detecting the presence of surveillance hardware in public spaces is legal in most jurisdictions. Always comply with local laws regarding wireless scanning and signal interception. The authors are not responsible for misuse.
+Passive reception of publicly-broadcast 802.11 frames for security research, privacy auditing, and education. The device does not transmit and does not authenticate to any network. Detecting the presence of surveillance hardware in public spaces is legal in most jurisdictions; always comply with local laws regarding wireless reception.
diff --git a/datasets/NitekryDPaul_wifi_ouis.md b/datasets/NitekryDPaul_wifi_ouis.md
index c5be892..fc4261d 100644
--- a/datasets/NitekryDPaul_wifi_ouis.md
+++ b/datasets/NitekryDPaul_wifi_ouis.md
@@ -10,7 +10,12 @@ Flock stations spend most of their duty cycle asleep, waking briefly to upload a
This addr1 technique is @NitekryDPaul's discovery and is the basis of the `promiscuis-flock-you` firmware.
-## OUI list (30 prefixes, lowercase, colon-separated)
+## OUI list (31 prefixes, lowercase, colon-separated)
+
+@NitekryDPaul contributed the first 30. The 31st (`82:6b:f2`) was contributed
+by **Michael / DeFlockJoplin** during follow-up drive-testing in Joplin — it's
+the OUI of the 12th camera in his field test, which the original list didn't
+catch. See [DeflockJoplin/flock-you](https://github.com/DeflockJoplin/flock-you).
```
70:c9:4e
@@ -43,6 +48,7 @@ ec:1b:bd
64:6e:69
48:27:ea
a4:cf:12
+82:6b:f2
```
## CSV form
@@ -79,6 +85,7 @@ a4:cf:12
| 64:6e:69 | Flock Safety infrastructure | WiFi 2.4 GHz | @NitekryDPaul |
| 48:27:ea | Flock Safety infrastructure | WiFi 2.4 GHz | @NitekryDPaul |
| a4:cf:12 | Flock Safety infrastructure | WiFi 2.4 GHz | @NitekryDPaul |
+| 82:6b:f2 | Flock Safety infrastructure | WiFi 2.4 GHz (wildcard probe) | Michael / DeFlockJoplin |
## Detection strategy
@@ -90,6 +97,20 @@ For each observed 802.11 management or data frame:
4. Match `addr1` (receiver) against the OUI list — **the addr1 insight**
5. Optional: match `addr3` (BSSID) on mgmt frames when addr2 is randomised
+### Wildcard-probe tightening (DeFlockJoplin)
+
+Michael / DeFlockJoplin observed that Flock cameras channel-hop and spam
+wildcard 802.11 Probe Requests on every channel. Combining that with the
+OUI match yields a very tight signature:
+
+1. Frame is Management, type=0 subtype=4 (Probe Request)
+2. SSID Information Element (tag 0) is present with length 0
+3. `addr2` (transmitter) matches the OUI list
+
+Field-tested in Joplin: **11 of 12 cameras caught with only 2 false
+positives**. The 12th camera used OUI `82:6b:f2`, which is now in the
+list above. Source: [DeflockJoplin/flock-you](https://github.com/DeflockJoplin/flock-you).
+
## Firmware
The `promiscuis-flock-you` firmware implementing this research is a mod of @NitekryDPaul's promiscuous-mode firmware. It emits Flask-compatible JSON over USB for ingestion by the `flock-you` dashboard and persists detections to on-device SPIFFS.
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..4621777
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,1122 @@
+#include Download current session to import into Flask dashboard
- - - -