Files
giglez/scripts/analyze_tembed_files.py
T
Trilltechnician 48fcb00241 Phase 3 Complete: Web Interface MVP
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>
2026-01-12 18:21:11 -08:00

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())