mirror of
https://github.com/colonelpanichacks/flock-you.git
synced 2026-04-23 21:39:59 -07:00
Overhaul firmware: BLE-only detection, web dashboard, GPS wardriving, session persistence
Complete rewrite of standalone Flock-You firmware: - Remove WiFi promiscuous mode, all detection is now BLE-only - Add web dashboard served on AP "flockyou" at 192.168.4.1 - GPS wardriving via phone browser Geolocation API - SPIFFS session persistence with auto-save every 60s - Prior session tab (PREV) survives reboots - KML export for Google Earth (current + prior session) - JSON/CSV export with GPS coordinates - Serial JSON output for Flask live ingestion - Crow call boot sounds with detection/heartbeat alerts - 200 unique device storage with FreeRTOS mutex - Flask app: add KML import endpoint, GPS data handling - Update platformio.ini with AsyncWebServer, ArduinoJson 7, SPIFFS partition - Rewrite README to reflect current functionality
This commit is contained in:
503
README.md
503
README.md
@@ -1,368 +1,149 @@
|
||||
# Flock You: Flock Safety Detection System
|
||||
# Flock-You: Surveillance Device Detector
|
||||
|
||||
<img src="flock.png" alt="Flock You" width="300px">
|
||||
|
||||
**Professional surveillance camera detection for the Oui-Spy device available at [colonelpanic.tech](https://colonelpanic.tech)**
|
||||
**Standalone BLE surveillance device detector with web dashboard, GPS wardriving, and session persistence.**
|
||||
|
||||
## Overview
|
||||
|
||||
Flock You is an advanced detection system designed to identify Flock Safety surveillance cameras, Raven gunshot detectors, and similar surveillance devices using multiple detection methodologies. Built for the Xiao ESP32 S3 microcontroller, it provides real-time monitoring with audio alerts and comprehensive JSON output. The system now includes specialized BLE service UUID fingerprinting for detecting SoundThinking/ShotSpotter Raven acoustic surveillance devices.
|
||||
|
||||
## Features
|
||||
|
||||
### Multi-Method Detection
|
||||
- **WiFi Promiscuous Mode**: Captures probe requests and beacon frames
|
||||
- **Bluetooth Low Energy (BLE) Scanning**: Monitors BLE advertisements
|
||||
- **MAC Address Filtering**: Detects devices by known MAC prefixes
|
||||
- **SSID Pattern Matching**: Identifies networks by specific names
|
||||
- **Device Name Pattern Matching**: Detects BLE devices by advertised names
|
||||
- **BLE Service UUID Detection**: Identifies Raven gunshot detectors by service UUIDs (NEW)
|
||||
|
||||
### Audio Alert System
|
||||
- **Boot Sequence**: 2 beeps (low pitch → high pitch) on startup
|
||||
- **Detection Alert**: 3 fast high-pitch beeps when device detected
|
||||
- **Heartbeat Pulse**: 2 beeps every 10 seconds while device remains in range
|
||||
- **Range Monitoring**: Automatic detection of device leaving range
|
||||
|
||||
### Comprehensive Output
|
||||
- **JSON Detection Data**: Structured output with timestamps, RSSI, MAC addresses
|
||||
- **Real-time Web Dashboard**: Live monitoring at `http://localhost:5000`
|
||||
- **Serial Terminal**: Real-time device output in the web interface
|
||||
- **Detection History**: Persistent storage and export capabilities (CSV, KML)
|
||||
- **Device Information**: Full device details including signal strength and threat assessment
|
||||
- **Detection Method Tracking**: Identifies which detection method triggered the alert
|
||||
|
||||
## Hardware Requirements
|
||||
|
||||
### Option 1: Oui-Spy Device (Available at colonelpanic.tech)
|
||||
- **Microcontroller**: Xiao ESP32 S3
|
||||
- **Wireless**: Dual WiFi/BLE scanning capabilities
|
||||
- **Audio**: Built-in buzzer system
|
||||
- **Connectivity**: USB-C for programming and power
|
||||
|
||||
### Option 2: Standard Xiao ESP32 S3 Setup
|
||||
- **Microcontroller**: Xiao ESP32 S3 board
|
||||
- **Buzzer**: 3V buzzer connected to GPIO3 (D2)
|
||||
- **Power**: USB-C cable for programming and power
|
||||
|
||||
### Wiring for Standard Setup
|
||||
```
|
||||
Xiao ESP32 S3 Buzzer
|
||||
GPIO3 (D2) ---> Positive (+)
|
||||
GND ---> Negative (-)
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
- PlatformIO IDE or PlatformIO Core
|
||||
- Python 3.8+ (for web interface)
|
||||
- USB-C cable for programming
|
||||
- Oui-Spy device from [colonelpanic.tech](https://colonelpanic.tech)
|
||||
|
||||
### Setup Instructions
|
||||
1. **Clone the repository**:
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd flock-you
|
||||
```
|
||||
|
||||
2. **Connect your Oui-Spy device** via USB-C
|
||||
|
||||
3. **Flash the firmware**:
|
||||
```bash
|
||||
pio run --target upload
|
||||
```
|
||||
|
||||
4. **Set up the web interface**:
|
||||
```bash
|
||||
cd api
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate # On Windows: venv\Scripts\activate
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
5. **Start the web server**:
|
||||
```bash
|
||||
python flockyou.py
|
||||
```
|
||||
|
||||
6. **Access the dashboard**:
|
||||
- Open your browser to `http://localhost:5000`
|
||||
- The web interface provides real-time detection monitoring
|
||||
- Serial terminal for device output
|
||||
- Detection history and export capabilities
|
||||
|
||||
7. **Monitor device output** (optional):
|
||||
```bash
|
||||
pio device monitor
|
||||
```
|
||||
|
||||
## Detection Coverage
|
||||
|
||||
### WiFi Detection Methods
|
||||
- **Probe Requests**: Captures devices actively searching for networks
|
||||
- **Beacon Frames**: Monitors network advertisements
|
||||
- **Channel Hopping**: Cycles through all 13 WiFi channels (2.4GHz)
|
||||
- **SSID Patterns**: Detects networks with "flock", "Penguin", "Pigvision" patterns
|
||||
- **MAC Prefixes**: Identifies devices by manufacturer MAC addresses
|
||||
|
||||
### BLE Detection Methods
|
||||
- **Advertisement Scanning**: Monitors BLE device broadcasts
|
||||
- **Device Names**: Matches against known surveillance device names
|
||||
- **MAC Address Filtering**: Detects devices by BLE MAC prefixes
|
||||
- **Service UUID Detection**: Identifies Raven devices by advertised service UUIDs
|
||||
- **Firmware Version Estimation**: Automatically determines Raven firmware version (1.1.x, 1.2.x, 1.3.x)
|
||||
- **Active Scanning**: Continuous monitoring with 100ms intervals
|
||||
|
||||
### Real-World Database Integration
|
||||
Detection patterns are derived from actual field data including:
|
||||
- Flock Safety camera signatures
|
||||
- Penguin surveillance device patterns
|
||||
- Pigvision system identifiers
|
||||
- Raven acoustic gunshot detection devices (SoundThinking/ShotSpotter)
|
||||
- Extended battery and external antenna configurations
|
||||
|
||||
**Datasets from deflock.me are included in the `datasets/` folder of this repository**, providing comprehensive device signatures and detection patterns for enhanced accuracy.
|
||||
|
||||
### Raven Gunshot Detection System
|
||||
Flock You now includes specialized detection for **Raven acoustic gunshot detection devices** (by SoundThinking/ShotSpotter) using BLE service UUID fingerprinting:
|
||||
|
||||
#### Detected Raven Services
|
||||
- **Device Information Service** (`0000180a-...`) - Serial number, model, firmware version
|
||||
- **GPS Location Service** (`00003100-...`) - Real-time device coordinates
|
||||
- **Power Management Service** (`00003200-...`) - Battery and solar panel status
|
||||
- **Network Status Service** (`00003300-...`) - LTE and WiFi connectivity information
|
||||
- **Upload Statistics Service** (`00003400-...`) - Data transmission metrics
|
||||
- **Error/Failure Service** (`00003500-...`) - System diagnostics and error logs
|
||||
- **Legacy Services** (`00001809-...`, `00001819-...`) - Older firmware versions (1.1.x)
|
||||
|
||||
#### Firmware Version Detection
|
||||
The system automatically identifies Raven firmware versions based on advertised services:
|
||||
- **1.1.x (Legacy)**: Uses Health Thermometer and Location/Navigation services
|
||||
- **1.2.x**: Introduces GPS, Power, and Network services
|
||||
- **1.3.x (Latest)**: Full suite of diagnostic and monitoring services
|
||||
|
||||
#### Raven Detection Output
|
||||
When a Raven device is detected, the system provides:
|
||||
- Device type identification: `RAVEN_GUNSHOT_DETECTOR`
|
||||
- Manufacturer: `SoundThinking/ShotSpotter`
|
||||
- Complete list of advertised service UUIDs
|
||||
- Service descriptions (GPS, Battery, Network status, etc.)
|
||||
- Estimated firmware version
|
||||
- Threat level: `CRITICAL` with score of 100
|
||||
|
||||
**Configuration data sourced from `raven_configurations.json`** (provided by [GainSec](https://github.com/GainSec)) in the datasets folder, containing verified service UUIDs from firmware versions 1.1.7, 1.2.0, and 1.3.1.
|
||||
|
||||
## Technical Specifications
|
||||
|
||||
### WiFi Capabilities
|
||||
- **Frequency**: 2.4GHz only (13 channels)
|
||||
- **Mode**: Promiscuous monitoring
|
||||
- **Channel Hopping**: Automatic cycling every 2 seconds
|
||||
- **Packet Types**: Probe requests (0x04) and beacons (0x08)
|
||||
|
||||
### BLE Capabilities
|
||||
- **Framework**: NimBLE-Arduino
|
||||
- **Scan Mode**: Active scanning
|
||||
- **Interval**: 100ms scan intervals
|
||||
- **Window**: 99ms scan windows
|
||||
|
||||
### Audio System
|
||||
- **Boot Sequence**: 200Hz → 800Hz (300ms each)
|
||||
- **Detection Alert**: 1000Hz × 3 beeps (150ms each)
|
||||
- **Heartbeat**: 600Hz × 2 beeps (100ms each, 100ms gap)
|
||||
- **Frequency**: Every 10 seconds while device in range
|
||||
|
||||
### JSON Output Format
|
||||
|
||||
#### WiFi Detection Example
|
||||
```json
|
||||
{
|
||||
"timestamp": 12345,
|
||||
"detection_time": "12.345s",
|
||||
"protocol": "wifi",
|
||||
"detection_method": "probe_request",
|
||||
"alert_level": "HIGH",
|
||||
"device_category": "FLOCK_SAFETY",
|
||||
"ssid": "Flock_Camera_001",
|
||||
"rssi": -65,
|
||||
"signal_strength": "MEDIUM",
|
||||
"channel": 6,
|
||||
"mac_address": "aa:bb:cc:dd:ee:ff",
|
||||
"threat_score": 95,
|
||||
"matched_patterns": ["ssid_pattern", "mac_prefix"],
|
||||
"device_info": {
|
||||
"manufacturer": "Flock Safety",
|
||||
"model": "Surveillance Camera",
|
||||
"capabilities": ["video", "audio", "gps"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Raven BLE Detection Example (NEW)
|
||||
```json
|
||||
{
|
||||
"protocol": "bluetooth_le",
|
||||
"detection_method": "raven_service_uuid",
|
||||
"device_type": "RAVEN_GUNSHOT_DETECTOR",
|
||||
"manufacturer": "SoundThinking/ShotSpotter",
|
||||
"mac_address": "12:34:56:78:9a:bc",
|
||||
"rssi": -72,
|
||||
"signal_strength": "MEDIUM",
|
||||
"device_name": "Raven-Device-001",
|
||||
"raven_service_uuid": "00003100-0000-1000-8000-00805f9b34fb",
|
||||
"raven_service_description": "GPS Location Service (Lat/Lon/Alt)",
|
||||
"raven_firmware_version": "1.3.x (Latest)",
|
||||
"threat_level": "CRITICAL",
|
||||
"threat_score": 100,
|
||||
"service_uuids": [
|
||||
"0000180a-0000-1000-8000-00805f9b34fb",
|
||||
"00003100-0000-1000-8000-00805f9b34fb",
|
||||
"00003200-0000-1000-8000-00805f9b34fb",
|
||||
"00003300-0000-1000-8000-00805f9b34fb",
|
||||
"00003400-0000-1000-8000-00805f9b34fb",
|
||||
"00003500-0000-1000-8000-00805f9b34fb"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Startup Sequence
|
||||
1. **Power on** the Oui-Spy device
|
||||
2. **Listen for boot beeps** (low → high pitch)
|
||||
3. **Start the web server**: `python flockyou.py` (from the `api` directory)
|
||||
4. **Open the dashboard**: Navigate to `http://localhost:5000`
|
||||
5. **Connect devices**: Use the web interface to connect your Flock You device and GPS
|
||||
6. **System ready** when "hunting for Flock Safety devices" appears in the serial terminal
|
||||
|
||||
### Detection Monitoring
|
||||
- **Web Dashboard**: Real-time detection display at `http://localhost:5000`
|
||||
- **Serial Terminal**: Live device output in the web interface
|
||||
- **Audio Alerts**: Immediate notification of detections (device-side)
|
||||
- **Heartbeat**: Continuous monitoring while devices in range
|
||||
- **Range Tracking**: Automatic detection of device departure
|
||||
- **Export Options**: Download detections as CSV or KML files
|
||||
|
||||
### Channel Information
|
||||
- **WiFi**: Automatically hops through channels 1-13
|
||||
- **BLE**: Continuous scanning across all BLE channels
|
||||
- **Status Updates**: Channel changes logged to serial terminal
|
||||
|
||||
## Detection Patterns
|
||||
|
||||
### SSID Patterns
|
||||
- `flock*` - Flock Safety cameras
|
||||
- `Penguin*` - Penguin surveillance devices
|
||||
- `Pigvision*` - Pigvision systems
|
||||
- `FS_*` - Flock Safety variants
|
||||
|
||||
### MAC Address Prefixes
|
||||
- `AA:BB:CC` - Flock Safety manufacturer codes
|
||||
- `DD:EE:FF` - Penguin device identifiers
|
||||
- `11:22:33` - Pigvision system codes
|
||||
|
||||
### BLE Device Names
|
||||
- `Flock*` - Flock Safety BLE devices
|
||||
- `Penguin*` - Penguin BLE identifiers
|
||||
- `Pigvision*` - Pigvision BLE devices
|
||||
|
||||
### Raven Service UUIDs (NEW)
|
||||
- `0000180a-0000-1000-8000-00805f9b34fb` - Device Information Service
|
||||
- `00003100-0000-1000-8000-00805f9b34fb` - GPS Location Service
|
||||
- `00003200-0000-1000-8000-00805f9b34fb` - Power Management Service
|
||||
- `00003300-0000-1000-8000-00805f9b34fb` - Network Status Service
|
||||
- `00003400-0000-1000-8000-00805f9b34fb` - Upload Statistics Service
|
||||
- `00003500-0000-1000-8000-00805f9b34fb` - Error/Failure Service
|
||||
- `00001809-0000-1000-8000-00805f9b34fb` - Health Service (Legacy 1.1.x)
|
||||
- `00001819-0000-1000-8000-00805f9b34fb` - Location Service (Legacy 1.1.x)
|
||||
|
||||
## Limitations
|
||||
|
||||
### Technical Constraints
|
||||
- **WiFi Range**: Limited to 2.4GHz spectrum
|
||||
- **Detection Range**: Approximately 50-100 meters depending on environment
|
||||
- **False Positives**: Possible with similar device signatures
|
||||
- **Battery Life**: Continuous scanning reduces battery runtime
|
||||
|
||||
### Environmental Factors
|
||||
- **Interference**: Other WiFi networks may affect detection
|
||||
- **Obstacles**: Walls and structures reduce detection range
|
||||
- **Weather**: Outdoor conditions may impact performance
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
1. **Web Server Won't Start**: Check Python version (3.8+) and virtual environment setup
|
||||
2. **No Serial Output**: Check USB connection and device port selection in web interface
|
||||
3. **No Audio**: Verify buzzer connection to GPIO3
|
||||
4. **No Detections**: Ensure device is in range and scanning is active
|
||||
5. **False Alerts**: Review detection patterns and adjust if needed
|
||||
6. **Connection Issues**: Verify device is connected via the web interface controls
|
||||
|
||||
### Debug Information
|
||||
- **Web Dashboard**: Real-time status and connection monitoring at `http://localhost:5000`
|
||||
- **Serial Terminal**: Live device output in the web interface
|
||||
- **Channel Hopping**: Logs channel changes for debugging
|
||||
- **Detection Logs**: Full JSON output for analysis
|
||||
|
||||
## Legal and Ethical Considerations
|
||||
|
||||
### Intended Use
|
||||
- **Research and Education**: Understanding surveillance technology
|
||||
- **Security Assessment**: Evaluating privacy implications
|
||||
- **Technical Analysis**: Studying wireless communication patterns
|
||||
|
||||
### Compliance
|
||||
- **Local Laws**: Ensure compliance with local regulations
|
||||
- **Privacy Rights**: Respect individual privacy and property rights
|
||||
- **Authorized Use**: Only use in authorized locations and situations
|
||||
|
||||
## Credits and Research
|
||||
|
||||
### Research Foundation
|
||||
This project is based on extensive research and public datasets from the surveillance detection community:
|
||||
|
||||
- **[DeFlock](https://deflock.me)** - Crowdsourced ALPR location and reporting tool
|
||||
- GitHub: [FoggedLens/deflock](https://github.com/FoggedLens/deflock)
|
||||
- Provides comprehensive datasets and methodologies for surveillance device detection
|
||||
- **Datasets included**: Real-world device signatures from deflock.me are included in the `datasets/` folder
|
||||
|
||||
- **[GainSec](https://github.com/GainSec)** - OSINT and privacy research
|
||||
- Specialized in surveillance technology analysis and detection methodologies
|
||||
- **Research referenced**: Some methodologies are based on their published research on surveillance technology
|
||||
- **Raven UUID Dataset Provider**: Contributed the `raven_configurations.json` dataset containing verified BLE service UUIDs from SoundThinking/ShotSpotter Raven devices across firmware versions 1.1.7, 1.2.0, and 1.3.1
|
||||
- Enables precise detection of Raven acoustic gunshot detection devices through BLE service UUID fingerprinting
|
||||
|
||||
### Methodology Integration
|
||||
Flock You unifies multiple known detection methodologies into a comprehensive scanner/wardriver specifically designed for Flock Safety cameras and similar surveillance devices. The system combines:
|
||||
|
||||
- **WiFi Promiscuous Monitoring**: Based on DeFlock's network analysis techniques
|
||||
- **BLE Device Detection**: Leveraging GainSec's Bluetooth surveillance research
|
||||
- **MAC Address Filtering**: Using crowdsourced device databases from deflock.me
|
||||
- **BLE Service UUID Fingerprinting**: Identifying Raven devices through advertised service characteristics
|
||||
- **Firmware Version Detection**: Analyzing service combinations to determine device capabilities
|
||||
- **Pattern Recognition**: Implementing research-based detection algorithms
|
||||
|
||||
### Acknowledgments
|
||||
Special thanks to the researchers and contributors who have made this work possible through their open-source contributions and public datasets:
|
||||
|
||||
- **GainSec** for providing the comprehensive Raven BLE service UUID dataset, enabling detection of SoundThinking/ShotSpotter acoustic surveillance devices
|
||||
- **DeFlock** for crowdsourced surveillance camera location data and detection methodologies
|
||||
- The broader surveillance detection community for their continued research and privacy protection efforts
|
||||
|
||||
This project builds upon their foundational work in surveillance detection and privacy protection.
|
||||
|
||||
|
||||
|
||||
### Purchase Information
|
||||
**Oui-Spy devices are available exclusively at [colonelpanic.tech](https://colonelpanic.tech)**
|
||||
|
||||
## License
|
||||
|
||||
This project is provided for educational and research purposes. Please ensure compliance with all applicable laws and regulations in your jurisdiction.
|
||||
Available as part of the OUI-SPY project at [colonelpanic.tech](https://colonelpanic.tech)
|
||||
|
||||
---
|
||||
|
||||
**Flock You: Professional surveillance detection for the privacy-conscious**
|
||||
## Overview
|
||||
|
||||
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.
|
||||
|
||||
No WiFi sniffing — the radio is dedicated to serving the dashboard AP while BLE scans continuously in the background via ESP32 coexistence.
|
||||
|
||||
---
|
||||
|
||||
## Detection Methods
|
||||
|
||||
All detection is BLE-based:
|
||||
|
||||
| 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 |
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- **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
|
||||
|
||||
---
|
||||
|
||||
## Enabling GPS (Android Chrome)
|
||||
|
||||
The dashboard uses your phone's GPS to geotag detections. Because it's served over HTTP, Chrome requires a one-time flag change:
|
||||
|
||||
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**
|
||||
|
||||
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.
|
||||
|
||||
> **Note:** iOS Safari does not support Geolocation over HTTP. GPS wardriving requires Android with Chrome.
|
||||
|
||||
---
|
||||
|
||||
## Hardware
|
||||
|
||||
**Board:** Seeed Studio XIAO ESP32-S3
|
||||
|
||||
| Pin | Function |
|
||||
|-----|----------|
|
||||
| GPIO 3 | Piezo buzzer |
|
||||
| GPIO 21 | LED (optional) |
|
||||
|
||||
---
|
||||
|
||||
## Building & Flashing
|
||||
|
||||
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
|
||||
|
||||
---
|
||||
|
||||
## Flask Companion App
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## Raven Gunshot Detector Detection
|
||||
|
||||
Flock-You identifies SoundThinking/ShotSpotter Raven devices through BLE service UUID fingerprinting:
|
||||
|
||||
| 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 |
|
||||
|
||||
Firmware version is estimated automatically from which service UUIDs are advertised.
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
- **[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
|
||||
|
||||
---
|
||||
|
||||
## Author
|
||||
|
||||
**colonelpanichacks**
|
||||
|
||||
**Oui-Spy devices available at [colonelpanic.tech](https://colonelpanic.tech)**
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
235
api/flockyou.py
235
api/flockyou.py
@@ -270,6 +270,18 @@ def flock_reader():
|
||||
try:
|
||||
data = json.loads(line)
|
||||
if 'detection_method' in data:
|
||||
# Map ESP32 GPS from phone to Flask GPS format
|
||||
esp_gps = data.get('gps')
|
||||
if esp_gps:
|
||||
data['gps'] = {
|
||||
'latitude': esp_gps.get('latitude'),
|
||||
'longitude': esp_gps.get('longitude'),
|
||||
'fix_quality': 1,
|
||||
'match_quality': 'esp32_phone_gps',
|
||||
'time_diff': 0,
|
||||
}
|
||||
if esp_gps.get('accuracy') is not None:
|
||||
data['gps']['accuracy'] = esp_gps['accuracy']
|
||||
# This is a detection, add it
|
||||
add_detection_from_serial(data)
|
||||
else:
|
||||
@@ -964,6 +976,229 @@ def export_kml():
|
||||
|
||||
return send_file(filepath, as_attachment=True, download_name=filename)
|
||||
|
||||
@app.route('/api/import/json', methods=['POST'])
|
||||
def import_json():
|
||||
"""Import detections from a JSON file (exported from ESP32 Flock-You dashboard)"""
|
||||
global detections, cumulative_detections, next_detection_id
|
||||
|
||||
if 'file' not in request.files:
|
||||
return jsonify({'status': 'error', 'message': 'No file provided'}), 400
|
||||
|
||||
file = request.files['file']
|
||||
if not file.filename:
|
||||
return jsonify({'status': 'error', 'message': 'No file selected'}), 400
|
||||
|
||||
try:
|
||||
content = file.read().decode('utf-8')
|
||||
imported = json.loads(content)
|
||||
|
||||
if not isinstance(imported, list):
|
||||
imported = [imported]
|
||||
|
||||
count = 0
|
||||
for item in imported:
|
||||
# Map ESP32 export fields to Flask detection format
|
||||
data = {
|
||||
'detection_method': item.get('method', item.get('detection_method', 'unknown')),
|
||||
'protocol': 'bluetooth_le',
|
||||
'mac_address': item.get('mac', item.get('mac_address', '')),
|
||||
'device_name': item.get('name', item.get('device_name', '')),
|
||||
'rssi': item.get('rssi', 0),
|
||||
'detection_count': item.get('count', item.get('detection_count', 1)),
|
||||
}
|
||||
|
||||
# Raven fields
|
||||
if item.get('raven') or item.get('is_raven'):
|
||||
data['is_raven'] = True
|
||||
data['raven_fw'] = item.get('fw', item.get('raven_fw', ''))
|
||||
|
||||
# GPS fields from ESP32 wardriving export
|
||||
gps_obj = item.get('gps')
|
||||
if gps_obj and (gps_obj.get('lat') or gps_obj.get('latitude')):
|
||||
data['gps'] = {
|
||||
'latitude': gps_obj.get('lat', gps_obj.get('latitude')),
|
||||
'longitude': gps_obj.get('lon', gps_obj.get('longitude')),
|
||||
'altitude': gps_obj.get('alt', gps_obj.get('altitude', 0)),
|
||||
'fix_quality': 1,
|
||||
'match_quality': 'esp32_phone_gps',
|
||||
'time_diff': 0,
|
||||
}
|
||||
if gps_obj.get('acc') is not None:
|
||||
data['gps']['accuracy'] = gps_obj['acc']
|
||||
|
||||
add_detection_from_serial(data)
|
||||
count += 1
|
||||
|
||||
print(f"Imported {count} detections from JSON file: {file.filename}")
|
||||
return jsonify({'status': 'success', 'message': f'Imported {count} detections', 'count': count})
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
return jsonify({'status': 'error', 'message': f'Invalid JSON: {str(e)}'}), 400
|
||||
except Exception as e:
|
||||
print(f"JSON import error: {e}")
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/import/csv', methods=['POST'])
|
||||
def import_csv():
|
||||
"""Import detections from a CSV file (exported from ESP32 Flock-You dashboard)"""
|
||||
global detections, cumulative_detections, next_detection_id
|
||||
|
||||
if 'file' not in request.files:
|
||||
return jsonify({'status': 'error', 'message': 'No file provided'}), 400
|
||||
|
||||
file = request.files['file']
|
||||
if not file.filename:
|
||||
return jsonify({'status': 'error', 'message': 'No file selected'}), 400
|
||||
|
||||
try:
|
||||
content = file.read().decode('utf-8')
|
||||
reader = csv.DictReader(content.splitlines())
|
||||
|
||||
count = 0
|
||||
for row in reader:
|
||||
data = {
|
||||
'detection_method': row.get('method', row.get('detection_method', 'unknown')),
|
||||
'protocol': 'bluetooth_le',
|
||||
'mac_address': row.get('mac', row.get('mac_address', '')),
|
||||
'device_name': row.get('name', row.get('device_name', '')),
|
||||
'rssi': int(row.get('rssi', 0)) if row.get('rssi') else 0,
|
||||
'detection_count': int(row.get('count', row.get('detection_count', 1))) if row.get('count', row.get('detection_count')) else 1,
|
||||
}
|
||||
|
||||
# Raven fields
|
||||
is_raven = row.get('is_raven', row.get('raven', 'false'))
|
||||
if is_raven and is_raven.lower() == 'true':
|
||||
data['is_raven'] = True
|
||||
data['raven_fw'] = row.get('raven_fw', row.get('fw', ''))
|
||||
|
||||
# GPS fields from ESP32 wardriving CSV export
|
||||
lat_str = row.get('latitude', '')
|
||||
lon_str = row.get('longitude', '')
|
||||
if lat_str and lon_str:
|
||||
try:
|
||||
data['gps'] = {
|
||||
'latitude': float(lat_str),
|
||||
'longitude': float(lon_str),
|
||||
'fix_quality': 1,
|
||||
'match_quality': 'esp32_phone_gps',
|
||||
'time_diff': 0,
|
||||
}
|
||||
acc_str = row.get('gps_accuracy', '')
|
||||
if acc_str:
|
||||
data['gps']['accuracy'] = float(acc_str)
|
||||
except (ValueError, TypeError):
|
||||
pass # Skip GPS if values are invalid
|
||||
|
||||
add_detection_from_serial(data)
|
||||
count += 1
|
||||
|
||||
print(f"Imported {count} detections from CSV file: {file.filename}")
|
||||
return jsonify({'status': 'success', 'message': f'Imported {count} detections', 'count': count})
|
||||
|
||||
except Exception as e:
|
||||
print(f"CSV import error: {e}")
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/import/kml', methods=['POST'])
|
||||
def import_kml():
|
||||
"""Import detections from a KML file (exported from ESP32 Flock-You dashboard)"""
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
if 'file' not in request.files:
|
||||
return jsonify({'status': 'error', 'message': 'No file provided'}), 400
|
||||
|
||||
file = request.files['file']
|
||||
if not file.filename:
|
||||
return jsonify({'status': 'error', 'message': 'No file selected'}), 400
|
||||
|
||||
try:
|
||||
content = file.read().decode('utf-8')
|
||||
root = ET.fromstring(content)
|
||||
|
||||
# Handle KML namespace
|
||||
ns = {'kml': 'http://www.opengis.net/kml/2.2'}
|
||||
placemarks = root.findall('.//kml:Placemark', ns)
|
||||
if not placemarks:
|
||||
# Try without namespace (some KML generators omit it)
|
||||
placemarks = root.findall('.//Placemark')
|
||||
|
||||
count = 0
|
||||
for pm in placemarks:
|
||||
name_el = pm.find('kml:name', ns) or pm.find('name')
|
||||
desc_el = pm.find('kml:description', ns) or pm.find('description')
|
||||
coord_el = pm.find('.//kml:coordinates', ns) or pm.find('.//coordinates')
|
||||
|
||||
mac = name_el.text.strip() if name_el is not None and name_el.text else f"unknown_{count}"
|
||||
|
||||
# Parse coordinates (lon,lat,alt)
|
||||
gps_data = None
|
||||
if coord_el is not None and coord_el.text:
|
||||
parts = coord_el.text.strip().split(',')
|
||||
if len(parts) >= 2:
|
||||
try:
|
||||
lon = float(parts[0])
|
||||
lat = float(parts[1])
|
||||
alt = float(parts[2]) if len(parts) > 2 else 0
|
||||
gps_data = {
|
||||
'latitude': lat,
|
||||
'longitude': lon,
|
||||
'altitude': alt,
|
||||
'fix_quality': 1,
|
||||
'match_quality': 'esp32_kml_import',
|
||||
'time_diff': 0,
|
||||
}
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# Parse description for metadata
|
||||
desc_text = desc_el.text.strip() if desc_el is not None and desc_el.text else ""
|
||||
device_name = ""
|
||||
method = "kml_import"
|
||||
rssi = 0
|
||||
det_count = 1
|
||||
|
||||
# Try to extract fields from CDATA description
|
||||
import re
|
||||
name_match = re.search(r'<b>Name:</b>\s*([^<]+)', desc_text)
|
||||
if name_match:
|
||||
device_name = name_match.group(1).strip()
|
||||
method_match = re.search(r'<b>Method:</b>\s*([^<]+)', desc_text)
|
||||
if method_match:
|
||||
method = method_match.group(1).strip()
|
||||
rssi_match = re.search(r'<b>RSSI:</b>\s*(-?\d+)', desc_text)
|
||||
if rssi_match:
|
||||
rssi = int(rssi_match.group(1))
|
||||
count_match = re.search(r'<b>Count:</b>\s*(\d+)', desc_text)
|
||||
if count_match:
|
||||
det_count = int(count_match.group(1))
|
||||
|
||||
data = {
|
||||
'detection_method': method,
|
||||
'protocol': 'bluetooth_le',
|
||||
'mac_address': mac,
|
||||
'device_name': device_name,
|
||||
'rssi': rssi,
|
||||
'detection_count': det_count,
|
||||
}
|
||||
|
||||
if gps_data:
|
||||
data['gps'] = gps_data
|
||||
|
||||
add_detection_from_serial(data)
|
||||
count += 1
|
||||
|
||||
print(f"Imported {count} detections from KML file: {file.filename}")
|
||||
return jsonify({'status': 'success', 'message': f'Imported {count} detections from KML', 'count': count})
|
||||
|
||||
except ET.ParseError as e:
|
||||
return jsonify({'status': 'error', 'message': f'Invalid KML: {str(e)}'}), 400
|
||||
except Exception as e:
|
||||
print(f"KML import error: {e}")
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
|
||||
@app.route('/api/clear', methods=['POST'])
|
||||
def clear_detections():
|
||||
"""Clear session detections"""
|
||||
|
||||
@@ -1376,6 +1376,15 @@
|
||||
<a href="#" onclick="exportKML('cumulative')">Cumulative KML</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="export-dropdown">
|
||||
<button class="export-btn dropdown-toggle" style="background:linear-gradient(135deg,#059669 0%,#10b981 100%);border-color:#047857" onclick="toggleImportDropdown()">Import ▼</button>
|
||||
<div class="export-dropdown-content" id="importDropdown">
|
||||
<a href="#" onclick="importFile('json')">Import JSON (from ESP32)</a>
|
||||
<a href="#" onclick="importFile('csv')">Import CSV (from ESP32)</a>
|
||||
<a href="#" onclick="importFile('kml')">Import KML (from ESP32)</a>
|
||||
</div>
|
||||
</div>
|
||||
<input type="file" id="importFileInput" accept=".json,.csv,.kml" style="display:none" onchange="handleImportFile()">
|
||||
<button class="clear-btn" onclick="clearDetections()">Clear All</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2023,17 +2032,78 @@
|
||||
function toggleExportDropdown() {
|
||||
const dropdown = document.getElementById('exportDropdown');
|
||||
dropdown.style.display = dropdown.style.display === 'block' ? 'none' : 'block';
|
||||
document.getElementById('importDropdown').style.display = 'none';
|
||||
}
|
||||
|
||||
function toggleImportDropdown() {
|
||||
const dropdown = document.getElementById('importDropdown');
|
||||
dropdown.style.display = dropdown.style.display === 'block' ? 'none' : 'block';
|
||||
document.getElementById('exportDropdown').style.display = 'none';
|
||||
}
|
||||
|
||||
function closeExportDropdown() {
|
||||
document.getElementById('exportDropdown').style.display = 'none';
|
||||
}
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
function closeImportDropdown() {
|
||||
document.getElementById('importDropdown').style.display = 'none';
|
||||
}
|
||||
|
||||
let pendingImportType = 'json';
|
||||
|
||||
function importFile(type) {
|
||||
pendingImportType = type;
|
||||
const input = document.getElementById('importFileInput');
|
||||
input.accept = type === 'json' ? '.json' : type === 'csv' ? '.csv' : '.kml';
|
||||
input.click();
|
||||
closeImportDropdown();
|
||||
}
|
||||
|
||||
function handleImportFile() {
|
||||
const input = document.getElementById('importFileInput');
|
||||
const file = input.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
const endpoint = pendingImportType === 'json' ? '/api/import/json' : pendingImportType === 'csv' ? '/api/import/csv' : '/api/import/kml';
|
||||
|
||||
fetch(endpoint, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
alert(data.message);
|
||||
loadDetections();
|
||||
loadCumulativeDetections();
|
||||
updateStats();
|
||||
} else {
|
||||
alert('Import error: ' + data.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Import error:', error);
|
||||
alert('Import failed: ' + error.message);
|
||||
});
|
||||
|
||||
// Reset input so same file can be re-selected
|
||||
input.value = '';
|
||||
}
|
||||
|
||||
// Close dropdowns when clicking outside
|
||||
document.addEventListener('click', function(event) {
|
||||
const dropdown = document.querySelector('.export-dropdown');
|
||||
if (!dropdown.contains(event.target)) {
|
||||
const exportDD = document.querySelector('.export-dropdown');
|
||||
const allDropdowns = document.querySelectorAll('.export-dropdown');
|
||||
let clickedInside = false;
|
||||
allDropdowns.forEach(dd => {
|
||||
if (dd.contains(event.target)) clickedInside = true;
|
||||
});
|
||||
if (!clickedInside) {
|
||||
closeExportDropdown();
|
||||
closeImportDropdown();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
5
partitions.csv
Normal file
5
partitions.csv
Normal file
@@ -0,0 +1,5 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x5000,
|
||||
otadata, data, ota, 0xe000, 0x2000,
|
||||
app0, app, ota_0, 0x10000, 0x600000,
|
||||
spiffs, data, spiffs, 0x610000, 0x1F0000,
|
||||
|
@@ -1,33 +1,30 @@
|
||||
[env:xiao_esp32s3]
|
||||
platform = espressif32
|
||||
platform = espressif32@^6.3.0
|
||||
board = seeed_xiao_esp32s3
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
board_build.partitions = huge_app.csv
|
||||
board_build.flash_mode = qio
|
||||
board_build.flash_size = 8MB
|
||||
board_build.psram_type = opi
|
||||
lib_deps =
|
||||
h2zero/NimBLE-Arduino@^1.4.0
|
||||
bblanchon/ArduinoJson@^6.21.0
|
||||
upload_speed = 921600
|
||||
|
||||
; Build options
|
||||
build_flags =
|
||||
-DARDUINO_USB_MODE=1
|
||||
-DCORE_DEBUG_LEVEL=0
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
-DBOARD_HAS_PSRAM
|
||||
-mfix-esp32-psram-cache-issue
|
||||
-DCONFIG_BT_NIMBLE_ENABLED=1
|
||||
|
||||
[env:xiao_esp32c3]
|
||||
platform = espressif32
|
||||
board = seeed_xiao_esp32c3
|
||||
framework = arduino
|
||||
monitor_speed = 115200
|
||||
board_build.partitions = huge_app.csv
|
||||
board_build.flash_mode = qio
|
||||
board_build.flash_size = 4MB
|
||||
board_build.psram_type = opi
|
||||
; Libraries
|
||||
lib_deps =
|
||||
h2zero/NimBLE-Arduino@^1.4.0
|
||||
bblanchon/ArduinoJson@^6.21.0
|
||||
build_flags =
|
||||
-DARDUINO_USB_MODE=1
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
-DCONFIG_BT_NIMBLE_ENABLED=1
|
||||
mathieucarbou/ESP Async WebServer@^3.0.6
|
||||
bblanchon/ArduinoJson@^7.0.4
|
||||
|
||||
; Board configuration
|
||||
board_build.arduino.memory_type = qio_opi
|
||||
board_build.partitions = partitions.csv
|
||||
board_build.filesystem = spiffs
|
||||
|
||||
; USB CDC configuration
|
||||
board_build.f_cpu = 240000000L
|
||||
board_build.f_flash = 80000000L
|
||||
board_build.flash_mode = qio
|
||||
|
||||
1463
src/main.cpp
1463
src/main.cpp
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user