48fcb00241
Major Achievements: - ✅ Full web interface (1,520+ lines of frontend code) - ✅ Interactive Leaflet.js map with marker clustering - ✅ Drag-and-drop upload system with GPS input - ✅ Search & filter UI with multi-criteria - ✅ Statistics dashboard with Chart.js - ✅ Responsive mobile-friendly design Backend: - ✅ FastAPI static file serving - ✅ Simplified server mode (main_simple.py) - ✅ Improved startup script with port auto-selection - ✅ PostgreSQL schema ready (requires setup) Database: - ✅ SQLite populated with 85 Flipper Zero signatures - ✅ Device matching system operational - ✅ Frequency-based search working Documentation: - ✅ PHASE_3_COMPLETE.md - Technical summary - ✅ WEB_INTERFACE_README.md - User guide - ✅ WEBAPP_STARTUP_GUIDE.md - Troubleshooting - ✅ POSTGRESQL_SETUP_EXPLANATION.md - DB setup guide - ✅ DATABASE_POPULATION_SUCCESS.md - Import report - ✅ DEVICE_IDENTIFICATION_REPORT.md - Matching analysis Files Created: - templates/index.html (260 lines) - static/css/main.css (500 lines) - static/js/*.js (760 lines total) - src/api/main_simple.py (simplified server) - start_web.sh (auto port selection) Status: Production MVP Ready Next: Phase 4 - API & Integration 🛰️ Generated with Claude Code https://claude.com/claude-code Co-Authored-By: Claude <noreply@anthropic.com>
235 lines
7.7 KiB
Python
Executable File
235 lines
7.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Analyze T-Embed RF files and extract signatures
|
|
|
|
Shows what RF patterns we can extract from T-Embed captures
|
|
without needing database connection
|
|
"""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import List, Dict, Any
|
|
|
|
# Add project root to path
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
|
from src.parser.sub_parser import SubFileParser
|
|
|
|
|
|
def analyze_rf_file(file_path: Path, parser: SubFileParser) -> Dict[str, Any]:
|
|
"""Analyze a single RF file and extract features"""
|
|
|
|
try:
|
|
metadata = parser.parse(str(file_path))
|
|
|
|
# Extract features
|
|
features = {
|
|
'filename': file_path.name,
|
|
'parsed': True,
|
|
'file_type': metadata.file_type,
|
|
'frequency_hz': metadata.frequency,
|
|
'frequency_mhz': metadata.frequency / 1e6 if metadata.frequency else 0,
|
|
'protocol': metadata.protocol or 'RAW',
|
|
'format': metadata.file_format,
|
|
'modulation': metadata.modulation or 'Unknown'
|
|
}
|
|
|
|
# RAW format specific features
|
|
if metadata.raw_data and len(metadata.raw_data) > 0:
|
|
abs_timings = [abs(t) for t in metadata.raw_data]
|
|
|
|
features.update({
|
|
'raw_samples': len(metadata.raw_data),
|
|
'timing_min': min(abs_timings),
|
|
'timing_max': max(abs_timings),
|
|
'timing_avg': sum(abs_timings) / len(abs_timings),
|
|
'timing_range': max(abs_timings) - min(abs_timings),
|
|
'raw_data_preview': metadata.raw_data[:20]
|
|
})
|
|
|
|
# Calculate pattern characteristics
|
|
features['pulse_count'] = len([t for t in metadata.raw_data if t > 0])
|
|
features['gap_count'] = len([t for t in metadata.raw_data if t < 0])
|
|
|
|
# KEY format specific features
|
|
if metadata.key_data:
|
|
features.update({
|
|
'key_data': metadata.key_data.hex(),
|
|
'key_length': len(metadata.key_data),
|
|
'bit_length': metadata.bit_length,
|
|
'timing_element': metadata.timing_element
|
|
})
|
|
|
|
# Empty file check
|
|
if metadata.frequency == 0 or (metadata.file_format == 'RAW' and not metadata.raw_data):
|
|
features['empty'] = True
|
|
else:
|
|
features['empty'] = False
|
|
|
|
return features
|
|
|
|
except Exception as e:
|
|
return {
|
|
'filename': file_path.name,
|
|
'parsed': False,
|
|
'error': str(e)
|
|
}
|
|
|
|
|
|
def generate_signature_from_features(features: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Generate a device signature from extracted features"""
|
|
|
|
if features.get('empty') or not features.get('parsed'):
|
|
return None
|
|
|
|
signature = {
|
|
'device_name': f"{features['filename'].replace('.sub', '')}_{features['frequency_mhz']:.0f}MHz",
|
|
'frequency': features['frequency_hz'],
|
|
'protocol': features['protocol'],
|
|
'modulation': features['modulation']
|
|
}
|
|
|
|
# Add timing signature for RAW
|
|
if 'timing_min' in features:
|
|
signature['timing_signature'] = {
|
|
'min': features['timing_min'],
|
|
'max': features['timing_max'],
|
|
'avg': features['timing_avg'],
|
|
'range': features['timing_range'],
|
|
'samples': features['raw_samples']
|
|
}
|
|
|
|
# Add pattern signature
|
|
if 'raw_data_preview' in features:
|
|
signature['pattern_preview'] = features['raw_data_preview']
|
|
|
|
# Guess device type from frequency
|
|
freq_mhz = features['frequency_mhz']
|
|
if 300 <= freq_mhz <= 350:
|
|
signature['likely_type'] = 'Garage Door / Gate Opener'
|
|
elif 400 <= freq_mhz <= 440:
|
|
signature['likely_type'] = 'Remote Control / Key Fob'
|
|
elif 860 <= freq_mhz <= 870:
|
|
signature['likely_type'] = 'Sensor / RFID'
|
|
elif 900 <= freq_mhz <= 930:
|
|
signature['likely_type'] = 'ISM Device / Sensor / IoT'
|
|
else:
|
|
signature['likely_type'] = 'Unknown'
|
|
|
|
return signature
|
|
|
|
|
|
def main():
|
|
"""Main entry point"""
|
|
|
|
print("="*80)
|
|
print("T-Embed RF File Analysis")
|
|
print("="*80)
|
|
print()
|
|
|
|
# Find T-Embed files
|
|
tembed_dir = Path(__file__).parent.parent / 'signatures' / 't-embed-rf'
|
|
|
|
if not tembed_dir.exists():
|
|
print(f"❌ Directory not found: {tembed_dir}")
|
|
return 1
|
|
|
|
sub_files = sorted(tembed_dir.glob('*.sub'))
|
|
print(f"Found {len(sub_files)} .sub files\n")
|
|
|
|
parser = SubFileParser()
|
|
|
|
all_features = []
|
|
signatures = []
|
|
|
|
# Analyze each file
|
|
for sub_file in sub_files:
|
|
print("-"*80)
|
|
features = analyze_rf_file(sub_file, parser)
|
|
all_features.append(features)
|
|
|
|
if not features.get('parsed'):
|
|
print(f"❌ {features['filename']}: {features.get('error', 'Unknown error')}\n")
|
|
continue
|
|
|
|
if features.get('empty'):
|
|
print(f"⏭️ {features['filename']}: Empty capture (skipped)\n")
|
|
continue
|
|
|
|
# Show analysis
|
|
print(f"✅ {features['filename']}")
|
|
print(f"\n Basic Info:")
|
|
print(f" File Type: {features['file_type']}")
|
|
print(f" Frequency: {features['frequency_mhz']:.2f} MHz ({features['frequency_hz']} Hz)")
|
|
print(f" Protocol: {features['protocol']}")
|
|
print(f" Format: {features['format']}")
|
|
print(f" Modulation: {features['modulation']}")
|
|
|
|
if 'raw_samples' in features:
|
|
print(f"\n RAW Signal Characteristics:")
|
|
print(f" Samples: {features['raw_samples']}")
|
|
print(f" Timing Range: {features['timing_min']}-{features['timing_max']} μs")
|
|
print(f" Average Timing: {features['timing_avg']:.1f} μs")
|
|
print(f" Pulse Count: {features['pulse_count']}")
|
|
print(f" Gap Count: {features['gap_count']}")
|
|
print(f" Preview: {features['raw_data_preview']}")
|
|
|
|
if 'key_data' in features:
|
|
print(f"\n KEY Format Data:")
|
|
print(f" Key: {features['key_data']}")
|
|
print(f" Bit Length: {features['bit_length']}")
|
|
print(f" Timing Element: {features['timing_element']}")
|
|
|
|
# Generate signature
|
|
sig = generate_signature_from_features(features)
|
|
if sig:
|
|
signatures.append(sig)
|
|
print(f"\n Device Signature:")
|
|
print(f" Device Name: {sig['device_name']}")
|
|
print(f" Likely Type: {sig['likely_type']}")
|
|
|
|
if 'timing_signature' in sig:
|
|
ts = sig['timing_signature']
|
|
print(f" Timing Signature: {ts['min']}-{ts['max']}μs (avg: {ts['avg']:.1f})")
|
|
|
|
print()
|
|
|
|
# Summary
|
|
print("="*80)
|
|
print("ANALYSIS SUMMARY")
|
|
print("="*80)
|
|
|
|
total = len(all_features)
|
|
parsed = sum(1 for f in all_features if f.get('parsed'))
|
|
empty = sum(1 for f in all_features if f.get('empty'))
|
|
valid = sum(1 for f in all_features if f.get('parsed') and not f.get('empty'))
|
|
|
|
print(f"Total files: {total}")
|
|
print(f"Successfully parsed: {parsed}")
|
|
print(f"Empty captures: {empty}")
|
|
print(f"Valid captures: {valid}")
|
|
|
|
print(f"\n Signatures Generated: {len(signatures)}")
|
|
|
|
if signatures:
|
|
print("\nSignature Database Preview:")
|
|
for i, sig in enumerate(signatures, 1):
|
|
print(f"\n{i}. {sig['device_name']}")
|
|
print(f" Frequency: {sig['frequency']/1e6:.2f} MHz")
|
|
print(f" Type: {sig['likely_type']}")
|
|
|
|
if 'timing_signature' in sig:
|
|
ts = sig['timing_signature']
|
|
print(f" Timing: {ts['min']}-{ts['max']} μs ({ts['samples']} samples)")
|
|
|
|
print("\n" + "="*80)
|
|
print("✅ Analysis complete!")
|
|
print("\nThese signatures can be imported into the database for device matching.")
|
|
print("="*80)
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|