feat: Integrate frequency-based device identification system
Integrated comprehensive device attribution system into GigLez:
1. Simple Device Matcher (src/matcher/simple_matcher.py):
- Frequency-based device categorization (315/433/868/915 MHz)
- Protocol-specific identification (Princeton, EV1527, Oregon Scientific, etc.)
- Modulation + frequency matching (OOK/FSK/ASK)
- Confidence scoring (0.4-0.95 range)
- 50+ device types covered
2. API Integration (src/api/main_simple.py):
- Device matching in upload pipeline
- Added device fields: device_name, device_category, match_confidence, match_method, device_description
- Top 5 alternative matches stored per capture
- New endpoint: GET /api/v1/captures/{id} for detail view
3. Frontend Implementation:
- Detail modal with comprehensive device information
- Device identification section with confidence bars
- Alternative matches display
- Signal, location, and metadata sections
- Keyboard (ESC) and click-outside modal closing
4. UI Enhancements (static/css/main.css):
- Modal overlay with backdrop blur
- Animated modal slide-in
- Confidence visualization (green/yellow/red bars)
- Responsive detail grid layout
- Device match cards with categories
5. JavaScript Integration:
- detail-modal.js: Comprehensive detail view renderer
- Updated map.js and search.js to use detail modal
- Removed placeholder functions
6. Utilities:
- scripts/rematch_captures.py: Re-run matcher on existing data
- Successfully re-matched 20 existing captures
Device Categories Supported:
- Consumer RF (remotes, sensors)
- Automotive (key fobs, TPMS)
- Home Automation (garage/gate openers, blinds)
- Sensors (weather stations, temperature)
- Security (door/window sensors, alarms)
- IoT (smart meters, LoRa devices)
- Industrial (SCADA, telemetry, RFID)
Match Methods:
- Protocol matching (highest confidence: 0.7-0.95)
- Frequency matching (0.4-0.7)
- Modulation + frequency matching (0.5-0.7)
Frontend now displays:
- Device name and category on map markers
- Confidence percentage
- Detailed device information modal
- Alternative device matches
- Match method explanation
All existing captures successfully identified with 60-70% confidence.
This commit is contained in:
+50
-3
@@ -21,6 +21,7 @@ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
||||
|
||||
from src.parser.sub_parser import SubFileParser
|
||||
from src.parser.gps_extractor import GPSFilenameExtractor
|
||||
from src.matcher.simple_matcher import get_matcher
|
||||
|
||||
# =============================================================================
|
||||
# APPLICATION INSTANCE
|
||||
@@ -151,6 +152,24 @@ async def get_captures():
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/v1/captures/{capture_id}")
|
||||
async def get_capture_detail(capture_id: int):
|
||||
"""Get detailed information for a specific capture"""
|
||||
# Find capture by ID
|
||||
capture = next((c for c in captures_storage if c.get("id") == capture_id), None)
|
||||
|
||||
if not capture:
|
||||
return {
|
||||
"success": False,
|
||||
"message": f"Capture with ID {capture_id} not found"
|
||||
}
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"capture": capture
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/v1/stats/summary")
|
||||
async def get_stats():
|
||||
"""Return stats from in-memory storage"""
|
||||
@@ -300,6 +319,18 @@ async def upload_captures(
|
||||
# Try to extract GPS from filename
|
||||
gps_coords = gps_extractor.extract(file.filename)
|
||||
|
||||
# Extract signal parameters
|
||||
frequency = metadata.frequency if hasattr(metadata, 'frequency') else 0
|
||||
protocol = metadata.protocol if hasattr(metadata, 'protocol') else "RAW"
|
||||
preset = metadata.preset if hasattr(metadata, 'preset') else "Unknown"
|
||||
|
||||
# Perform device matching
|
||||
matcher = get_matcher()
|
||||
matches = matcher.match(frequency, protocol, preset)
|
||||
|
||||
# Get best match
|
||||
best_match = matches[0] if matches else None
|
||||
|
||||
# Use GPS from manifest or filename
|
||||
global upload_counter
|
||||
upload_counter += 1
|
||||
@@ -307,15 +338,31 @@ async def upload_captures(
|
||||
capture_info = {
|
||||
"id": upload_counter,
|
||||
"filename": file.filename,
|
||||
"frequency": metadata.frequency if hasattr(metadata, 'frequency') else 0,
|
||||
"protocol": metadata.protocol if hasattr(metadata, 'protocol') else "RAW",
|
||||
"preset": metadata.preset if hasattr(metadata, 'preset') else "Unknown",
|
||||
"frequency": frequency,
|
||||
"protocol": protocol,
|
||||
"preset": preset,
|
||||
"latitude": gps_coords.latitude if gps_coords else manifest_data.get("captures", [{}])[0].get("latitude"),
|
||||
"longitude": gps_coords.longitude if gps_coords else manifest_data.get("captures", [{}])[0].get("longitude"),
|
||||
"gps_source": gps_coords.source if gps_coords else "manual",
|
||||
"timestamp": manifest_data.get("captures", [{}])[0].get("timestamp", ""),
|
||||
"data_source": manifest_data.get("data_source", "production"), # production, test, or mock
|
||||
"session_id": manifest_data.get("session_uuid", ""),
|
||||
# Device identification fields
|
||||
"device_name": best_match.device_name if best_match else None,
|
||||
"device_category": best_match.device_category if best_match else None,
|
||||
"match_confidence": best_match.confidence if best_match else None,
|
||||
"match_method": best_match.match_method if best_match else None,
|
||||
"device_description": best_match.description if best_match else None,
|
||||
"matched_devices": [
|
||||
{
|
||||
"device_name": m.device_name,
|
||||
"category": m.device_category,
|
||||
"confidence": m.confidence,
|
||||
"method": m.match_method,
|
||||
"description": m.description
|
||||
}
|
||||
for m in matches[:5] # Top 5 matches
|
||||
] if matches else []
|
||||
}
|
||||
|
||||
# Store in memory
|
||||
|
||||
Reference in New Issue
Block a user