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