4237c4bdb8
## Phase 1: Code Cleanup (~1,859 lines removed) **Deleted Redundant Matchers:** - ❌ strategies_orm.py (356 lines) - Old ORM-based strategies - ❌ simple_matcher.py (351 lines) - Replaced by strategies.py - ❌ rtl433_matcher.py (352 lines) - Replaced by strategies.py **Archived Old Scripts:** - Moved 11 one-time analysis/import scripts to scripts/archive/ - Scripts: analyze_flipper_signatures, analyze_tembed_files, identify_tembed_devices, import_flipper_sqlite, import_tembed_signatures, match_tembed_with_db, match_with_flipper_db, rematch_captures, test_gps_extraction, test_tembed_matching, test_wardriving_import **Consolidated API:** - Renamed main.py → main_orm_legacy.py (archived old ORM-based API) - main_simple.py is now the primary production API ## Phase 2: Pattern Decoder Integration ✅ **CRITICAL FIX: Pattern decoder now integrated into production API!** **Changes:** 1. Updated main_simple.py to use unified SignatureMatcher 2. Added 6 strategies to matcher pipeline: - ExactMatcher (protocol + frequency) - FrequencyMatcher (frequency-based) - BitPatternMatcher (data patterns) - TimingMatcher (timing-based) - RTL433DecoderStrategy (RTL_433 decoder) - PatternBasedStrategy (NEW! Pattern decoder for short captures) 3. Created MockDB class for simplified mode (no real database) 4. Replaced old get_matcher() with get_matcher_engine() 5. Updated upload handler to use MatchResult format 6. All matches now include confidence scores and match methods **Result:** - Pattern decoder is NOW ACTIVE in production 🎉 - Unified matching pipeline with 6 strategies - Cleaner codebase (-1,859 lines) - Single source of truth for matching logic 🎉 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
245 lines
6.4 KiB
Python
245 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Import Flipper Zero signatures into SQLite database
|
|
|
|
Uses SQLite for immediate testing without PostgreSQL setup
|
|
"""
|
|
|
|
import sys
|
|
import sqlite3
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
|
|
# Add project root to path
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
|
from src.parser.sub_parser import SubFileParser
|
|
|
|
|
|
def create_sqlite_schema(conn):
|
|
"""Create SQLite schema"""
|
|
|
|
cursor = conn.cursor()
|
|
|
|
# Devices table
|
|
cursor.execute('''
|
|
CREATE TABLE IF NOT EXISTS devices (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
device_name TEXT,
|
|
manufacturer TEXT,
|
|
model TEXT,
|
|
device_type TEXT,
|
|
typical_frequency INTEGER,
|
|
protocol TEXT,
|
|
description TEXT,
|
|
first_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
is_verified BOOLEAN DEFAULT 0,
|
|
source TEXT
|
|
)
|
|
''')
|
|
|
|
# Signatures table
|
|
cursor.execute('''
|
|
CREATE TABLE IF NOT EXISTS signatures (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
device_id INTEGER REFERENCES devices(id),
|
|
protocol TEXT,
|
|
frequency INTEGER,
|
|
modulation TEXT,
|
|
bit_pattern BLOB,
|
|
bit_mask BLOB,
|
|
timing_min INTEGER,
|
|
timing_max INTEGER,
|
|
raw_pattern TEXT,
|
|
confidence_threshold REAL DEFAULT 0.7,
|
|
source TEXT,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
)
|
|
''')
|
|
|
|
# Indexes
|
|
cursor.execute('CREATE INDEX IF NOT EXISTS idx_sig_freq ON signatures(frequency)')
|
|
cursor.execute('CREATE INDEX IF NOT EXISTS idx_sig_device ON signatures(device_id)')
|
|
|
|
conn.commit()
|
|
|
|
|
|
def import_flipper_signature(conn, sub_file: Path, parser: SubFileParser):
|
|
"""Import a single Flipper Zero .sub file"""
|
|
|
|
try:
|
|
metadata = parser.parse(str(sub_file))
|
|
|
|
# Skip if frequency is 0
|
|
if metadata.frequency == 0:
|
|
return None
|
|
|
|
cursor = conn.cursor()
|
|
|
|
# Determine device type from frequency
|
|
freq_mhz = metadata.frequency / 1e6
|
|
|
|
if 300 <= freq_mhz <= 350:
|
|
device_type = 'garage_door'
|
|
elif 400 <= freq_mhz <= 450:
|
|
device_type = 'remote_control'
|
|
elif 800 <= freq_mhz <= 900:
|
|
device_type = 'sensor'
|
|
else:
|
|
device_type = 'unknown'
|
|
|
|
# Create device record
|
|
cursor.execute('''
|
|
INSERT INTO devices (device_name, manufacturer, model, device_type,
|
|
typical_frequency, protocol, description, source)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
''', (
|
|
sub_file.stem,
|
|
'Unknown',
|
|
sub_file.stem,
|
|
device_type,
|
|
metadata.frequency,
|
|
metadata.protocol or 'RAW',
|
|
f'Imported from Flipper Zero: {sub_file.name}',
|
|
'flipper_zero'
|
|
))
|
|
|
|
device_id = cursor.lastrowid
|
|
|
|
# Extract timing info
|
|
timing_min, timing_max = None, None
|
|
raw_pattern = None
|
|
|
|
if metadata.raw_data and len(metadata.raw_data) > 0:
|
|
abs_timings = [abs(t) for t in metadata.raw_data]
|
|
timing_min = min(abs_timings)
|
|
timing_max = max(abs_timings)
|
|
raw_pattern = ','.join(map(str, metadata.raw_data[:100])) # First 100 samples
|
|
|
|
# Create signature record
|
|
cursor.execute('''
|
|
INSERT INTO signatures (device_id, protocol, frequency, modulation,
|
|
timing_min, timing_max, raw_pattern, source)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
''', (
|
|
device_id,
|
|
metadata.protocol or 'RAW',
|
|
metadata.frequency,
|
|
metadata.modulation,
|
|
timing_min,
|
|
timing_max,
|
|
raw_pattern,
|
|
'flipper_zero'
|
|
))
|
|
|
|
conn.commit()
|
|
|
|
return {
|
|
'device_id': device_id,
|
|
'device_name': sub_file.stem,
|
|
'frequency': metadata.frequency,
|
|
'protocol': metadata.protocol
|
|
}
|
|
|
|
except Exception as e:
|
|
return None
|
|
|
|
|
|
def main():
|
|
"""Main entry point"""
|
|
|
|
print("="*80)
|
|
print("FLIPPER ZERO → SQLite IMPORT")
|
|
print("="*80)
|
|
print()
|
|
|
|
# Create/connect to SQLite database
|
|
db_path = Path(__file__).parent.parent / 'giglez.db'
|
|
|
|
print(f"Database: {db_path}")
|
|
|
|
conn = sqlite3.connect(str(db_path))
|
|
print("✅ Connected to SQLite database\n")
|
|
|
|
# Create schema
|
|
print("Creating schema...")
|
|
create_sqlite_schema(conn)
|
|
print("✅ Schema ready\n")
|
|
|
|
# Find Flipper signatures
|
|
flipper_dir = Path(__file__).parent.parent / 'signatures' / 'flipperzero-firmware'
|
|
|
|
if not flipper_dir.exists():
|
|
print(f"❌ Flipper directory not found: {flipper_dir}")
|
|
return 1
|
|
|
|
sub_files = list(flipper_dir.glob('**/*.sub'))
|
|
print(f"Found {len(sub_files)} Flipper Zero .sub files\n")
|
|
|
|
# Import all signatures
|
|
parser = SubFileParser()
|
|
imported = []
|
|
skipped = 0
|
|
|
|
print("Importing signatures...")
|
|
for i, sub_file in enumerate(sub_files, 1):
|
|
if i % 10 == 0:
|
|
print(f" Processed {i}/{len(sub_files)}...")
|
|
|
|
result = import_flipper_signature(conn, sub_file, parser)
|
|
|
|
if result:
|
|
imported.append(result)
|
|
else:
|
|
skipped += 1
|
|
|
|
print(f"✅ Import complete\n")
|
|
|
|
# Summary
|
|
print("="*80)
|
|
print("IMPORT SUMMARY")
|
|
print("="*80)
|
|
print(f"Total files: {len(sub_files)}")
|
|
print(f"Imported: {len(imported)}")
|
|
print(f"Skipped: {skipped}")
|
|
|
|
# Query database
|
|
cursor = conn.cursor()
|
|
|
|
print(f"\nDATABASE CONTENTS")
|
|
print("-"*80)
|
|
|
|
cursor.execute("SELECT COUNT(*) FROM devices")
|
|
device_count = cursor.fetchone()[0]
|
|
print(f"Devices: {device_count}")
|
|
|
|
cursor.execute("SELECT COUNT(*) FROM signatures")
|
|
sig_count = cursor.fetchone()[0]
|
|
print(f"Signatures: {sig_count}")
|
|
|
|
# Frequency distribution
|
|
print(f"\nFREQUENCY DISTRIBUTION")
|
|
print("-"*80)
|
|
|
|
cursor.execute('''
|
|
SELECT frequency, COUNT(*) as count
|
|
FROM signatures
|
|
GROUP BY frequency
|
|
ORDER BY count DESC
|
|
''')
|
|
|
|
for freq, count in cursor.fetchall():
|
|
print(f"{freq/1e6:8.2f} MHz: {count:3d} devices")
|
|
|
|
conn.close()
|
|
|
|
print(f"\n{'='*80}")
|
|
print("✅ Database ready at:", db_path)
|
|
print("="*80)
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|