GPS Auto-Extraction + First Successful Upload Complete
Major milestone: GPS coordinates now auto-extract from filenames and uploads appear on map with full end-to-end workflow functional! ✨ GPS Auto-Extraction Features: - JavaScript GPS extractor class matching Python patterns - Supports 3 filename formats: * N/S/E/W: 34.0478N_118.2349W_filename.sub * lat/lon prefix: lat34.0478lon-118.2348_filename.sub * Signed decimal: -34.0478_118.2348_filename.sub - Auto-populates latitude/longitude form fields on file drop - Green notification toast shows detected coordinates - File list shows GPS badge for files with coordinates 🗺️ Web Interface Improvements: - Upload endpoint now stores captures in-memory - Query endpoint returns uploaded captures for map display - Stats endpoint shows real-time upload counts - Map displays uploaded captures as markers - Color-coded by frequency band 📁 Updated Files: - static/js/upload.js: GPS extraction + auto-population - src/api/main_simple.py: In-memory storage + endpoints - src/parser/gps_extractor.py: Backend GPS extraction (Python) - scripts/test_gps_extraction.py: Python test suite - test_gps_extraction.html: Browser test suite 📊 T-Embed Files Updated: - 34.0478N_118.2348W_1637_raw_8.sub: 315 MHz Princeton - 34.0478N_118.2349W_1351_raw_10.sub: 433.92 MHz Princeton - 34.0478N_118.2349W_1650_test_raw.sub: 433.92 MHz RAW - All now have proper Flipper SubGhz headers ✅ Tested Features: - GPS extraction from filename: 34.0478N_118.2349W → 34.0478, -118.2349 - Auto-population of GPS fields in upload form - File upload with GPS validation - Capture appears on map after upload - Statistics update in real-time - Frequency distribution calculated correctly 🎯 End-to-End Flow Working: 1. User drops .sub file with GPS in filename 2. GPS auto-detected and form fields populate 3. User clicks Upload 4. Server parses RF data + GPS coordinates 5. Capture stored in memory 6. Map refreshes and displays new marker 7. Stats update with new counts 🚀 Demo: http://localhost:8000 Upload 34.0478N_118.2349W_1351_raw_10.sub and watch it appear on map! 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Executable
+178
@@ -0,0 +1,178 @@
|
||||
#!/data/data/com.termux/files/usr/bin/python3
|
||||
"""
|
||||
Quick GPS+RF Capture - Uses existing wardriving infrastructure
|
||||
Captures current GPS and associates it with a Bruce .sub file
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import subprocess
|
||||
import hashlib
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
# Import existing infrastructure
|
||||
from database import SubGHzWardrivingDB
|
||||
|
||||
|
||||
def get_current_gps(timeout=15):
|
||||
"""Get current GPS using termux-location."""
|
||||
try:
|
||||
cmd = ["termux-location", "-p", "gps"]
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
if result.returncode == 0 and result.stdout.strip():
|
||||
gps_data = json.loads(result.stdout.strip())
|
||||
if 'latitude' in gps_data and 'longitude' in gps_data:
|
||||
print(f"✓ GPS Location: {gps_data['latitude']:.6f}, {gps_data['longitude']:.6f}")
|
||||
print(f" Accuracy: {gps_data.get('accuracy', 'N/A')}m")
|
||||
return gps_data
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
print(f"✗ GPS timeout after {timeout}s")
|
||||
except Exception as e:
|
||||
print(f"✗ GPS error: {e}")
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def parse_sub_file(sub_file):
|
||||
"""Parse Bruce .sub file to extract RF metadata."""
|
||||
metadata = {}
|
||||
|
||||
try:
|
||||
with open(sub_file, 'r') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or not ':' in line:
|
||||
continue
|
||||
|
||||
key, value = line.split(':', 1)
|
||||
key = key.strip()
|
||||
value = value.strip()
|
||||
|
||||
if key == 'Frequency':
|
||||
metadata['frequency_hz'] = int(value)
|
||||
elif key == 'Protocol':
|
||||
metadata['protocol'] = value
|
||||
elif key == 'Preset':
|
||||
metadata['preset'] = value
|
||||
|
||||
return metadata if metadata else None
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Error parsing {sub_file}: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def calculate_file_hash(file_path):
|
||||
"""Calculate MD5 hash of file."""
|
||||
md5 = hashlib.md5()
|
||||
with open(file_path, 'rb') as f:
|
||||
for chunk in iter(lambda: f.read(4096), b''):
|
||||
md5.update(chunk)
|
||||
return md5.hexdigest()
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: quick_capture.py <bruce_file.sub> [session_id]")
|
||||
print("\nExample:")
|
||||
print(" python3 quick_capture.py raw10.sub")
|
||||
print(" python3 quick_capture.py ~/projects/device-integration/bruce/data/captures/raw10.sub mysession")
|
||||
return 1
|
||||
|
||||
sub_file = Path(sys.argv[1])
|
||||
session_id = sys.argv[2] if len(sys.argv) > 2 else f"quick_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
||||
|
||||
if not sub_file.exists():
|
||||
print(f"✗ File not found: {sub_file}")
|
||||
return 1
|
||||
|
||||
print(f"\n📡 SubGHz GPS Capture")
|
||||
print(f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
||||
print(f"Bruce File: {sub_file.name}")
|
||||
print(f"Session: {session_id}")
|
||||
print()
|
||||
|
||||
# Get GPS
|
||||
print("🛰️ Getting GPS location...")
|
||||
gps_data = get_current_gps()
|
||||
if not gps_data:
|
||||
print("\n✗ Failed to get GPS - trying to use latest from log...")
|
||||
gps_log = Path.home() / "logs" / "gps_quick.jsonl"
|
||||
if gps_log.exists():
|
||||
with open(gps_log, 'r') as f:
|
||||
last_line = f.readlines()[-1]
|
||||
gps_data = json.loads(last_line.strip())
|
||||
print(f"✓ Using latest GPS: {gps_data['latitude']:.6f}, {gps_data['longitude']:.6f}")
|
||||
else:
|
||||
print("✗ No GPS data available")
|
||||
return 1
|
||||
|
||||
# Parse RF file
|
||||
print("\n📻 Parsing RF file...")
|
||||
rf_metadata = parse_sub_file(sub_file)
|
||||
if not rf_metadata:
|
||||
print("✗ Failed to parse RF file")
|
||||
return 1
|
||||
|
||||
print(f"✓ Frequency: {rf_metadata.get('frequency_hz', 'N/A')} Hz ({rf_metadata.get('frequency_hz', 0) / 1e6:.3f} MHz)")
|
||||
print(f" Protocol: {rf_metadata.get('protocol', 'N/A')}")
|
||||
print(f" Preset: {rf_metadata.get('preset', 'N/A')}")
|
||||
|
||||
# Initialize database
|
||||
print("\n💾 Storing to database...")
|
||||
db = SubGHzWardrivingDB()
|
||||
|
||||
# Create session if it doesn't exist
|
||||
db.create_session(session_id, device_info="Bruce T-Embed CC1101", notes="Quick GPS capture")
|
||||
|
||||
# Add GPS track point
|
||||
db.add_gps_point(
|
||||
session_id=session_id,
|
||||
latitude=gps_data['latitude'],
|
||||
longitude=gps_data['longitude'],
|
||||
altitude=gps_data.get('altitude'),
|
||||
accuracy=gps_data.get('accuracy'),
|
||||
speed=gps_data.get('speed'),
|
||||
provider=gps_data.get('provider', 'gps')
|
||||
)
|
||||
|
||||
# Add signal capture
|
||||
timestamp = datetime.now(timezone.utc).isoformat()
|
||||
capture_id = f"{session_id}_{sub_file.stem}_{int(datetime.now().timestamp())}"
|
||||
|
||||
db.add_signal(
|
||||
session_id=session_id,
|
||||
capture_id=capture_id,
|
||||
timestamp=timestamp,
|
||||
bruce_filename=sub_file.name,
|
||||
bruce_file_path=str(sub_file),
|
||||
file_size_bytes=sub_file.stat().st_size,
|
||||
frequency_hz=rf_metadata.get('frequency_hz'),
|
||||
protocol=rf_metadata.get('protocol'),
|
||||
gps_latitude=gps_data['latitude'],
|
||||
gps_longitude=gps_data['longitude'],
|
||||
gps_accuracy_meters=gps_data.get('accuracy'),
|
||||
interpolated=False,
|
||||
interpolation_confidence=1.0,
|
||||
file_hash_md5=calculate_file_hash(sub_file)
|
||||
)
|
||||
|
||||
print(f"✓ Stored signal: {capture_id}")
|
||||
print(f"\n✅ Capture paired successfully!")
|
||||
print(f" Database: ~/logs/subghz_wardriving.db")
|
||||
print(f" Export with: python3 -m subghz_wardriving.exporter")
|
||||
print()
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user