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:
Colonel Panic
2026-02-07 12:53:58 -05:00
parent be1f3acde2
commit 3606f1f812
6 changed files with 1332 additions and 993 deletions

503
README.md
View File

@@ -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.

View File

@@ -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"""

View File

@@ -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
View 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 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0xe000 0x2000
4 app0 app ota_0 0x10000 0x600000
5 spiffs data spiffs 0x610000 0x1F0000

View File

@@ -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

File diff suppressed because it is too large Load Diff