Files
giglez/scripts/test_pattern_decoder.py
Trilltechnician 9be14af160 Implement pattern-based decoder for single-transmission RF captures
Added pattern-based decoding system specifically designed for short captures
from Flipper Zero and LilyGo T-Embed devices that don't have enough repetitions
for RTL_433.

## New Components:

1. **Protocol Database** (protocol_database.py)
   - 18 known RF protocol signatures
   - Categories: Weather Sensors, Garage Doors, TPMS, Doorbells, etc.
   - Timing patterns for Acurite, Oregon Scientific, LaCrosse, Nexus, etc.

2. **Pattern Decoder** (pattern_decoder.py)
   - Multi-strategy decoder using 3 approaches:
     - Timing pattern analysis (K-means clustering for SHORT/LONG pulses)
     - Statistical fingerprinting (signal characteristics)
     - Protocol library matching
   - Works with single-transmission captures (100-500 pulses)

3. **Matcher Integration** (strategies.py)
   - Added PatternBasedStrategy to matcher pipeline
   - Integrates with existing MatchResult system
   - Confidence scoring: 0.5-0.9 based on match quality

## Test Results:

**Pattern Decoder vs RTL_433 Performance:**
- RTL_433: 0% decode rate (0/8 files) - requires multiple repetitions
- Pattern Decoder: 44.4% decode rate (4/9 files) - works with single captures

**Successful Decodes:**
- Oregon Scientific weather sensors (76% confidence)
- Acurite weather stations (52% confidence)
- 24 total device matches across 4 files

## Implementation Details:

- K-means clustering for pulse width identification
- Statistical fingerprinting with mean, std, duty cycle
- Protocol database with 7 weather sensors + 11 other device types
- Confidence thresholds optimized for single-tx captures
- Fallback to sklearn K-means or percentile-based clustering

## Documentation:

- PATTERN_BASED_DECODING_PLAN.md: Complete implementation plan
- test_pattern_decoder.py: Comprehensive test suite

Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-14 19:22:07 -08:00

243 lines
7.6 KiB
Python

#!/usr/bin/env python3
"""
Test Pattern Decoder with Real RF Captures
Tests the pattern-based decoder against the same weather sensor captures
that failed with RTL_433 (0% decode rate).
"""
import sys
from pathlib import Path
# Add project to path
sys.path.insert(0, str(Path(__file__).parent.parent))
from src.parser.sub_parser import parse_sub_file
from src.matcher.pattern_decoder import get_pattern_decoder
def test_pattern_decoder(test_dir="data/test_rtl433_real"):
"""Test pattern decoder against real weather sensor captures"""
test_path = Path(test_dir)
if not test_path.exists():
print(f"❌ Test directory not found: {test_dir}")
return False
# Get pattern decoder
decoder = get_pattern_decoder()
print("=" * 80)
print("Testing Pattern Decoder with Real Weather Sensor Captures")
print("=" * 80)
print()
print(f"Source: FlipperZero-Subghz-DB (previously tested with RTL_433)")
print(f"RTL_433 Result: 0% decode rate (0/8 files)")
print(f"Test Directory: {test_path}")
print(f"Decoder Version: {decoder.get_version()}")
print()
# Get all .sub files
sub_files = sorted(test_path.glob("*.sub"))
if not sub_files:
print(f"❌ No .sub files found in {test_dir}")
return False
print(f"Testing {len(sub_files)} weather sensor captures")
print()
# Test each file
results = []
successful_decodes = 0
total_devices_decoded = 0
for i, sub_file in enumerate(sub_files, 1):
print("─" * 80)
print(f"[{i}/{len(sub_files)}] {sub_file.name}")
print("─" * 80)
try:
# Parse .sub file
metadata = parse_sub_file(str(sub_file))
print(f"Protocol: {metadata.protocol or 'RAW'}")
print(f"Frequency: {metadata.frequency / 1_000_000:.3f} MHz")
print(f"Format: {metadata.file_format}")
if metadata.has_raw_data:
print(f"RAW Data: {metadata.pulse_count} pulses")
# Show first few pulses for debugging
pulses = metadata.raw_data[:10]
print(f"First pulses: {pulses}")
else:
print(f"⚠️ No RAW data - skipping")
results.append({
"filename": sub_file.name,
"decoded": False,
"reason": "No RAW data"
})
print()
continue
print()
# Try pattern decoding
print("🔍 Running Pattern Decoder...")
matches = decoder.decode(metadata)
if matches:
successful_decodes += 1
total_devices_decoded += len(matches)
print(f"✅ SUCCESS! Decoded {len(matches)} device(s):")
print()
for j, match in enumerate(matches, 1):
print(f" Match #{j}:")
print(f" Device: {match.name}")
print(f" Manufacturer: {match.manufacturer or 'Unknown'}")
print(f" Category: {match.category}")
print(f" Confidence: {match.confidence:.2%}")
print(f" Method: {match.match_method}")
# Show details
if match.details:
print(f" Details:")
for key, value in match.details.items():
print(f" {key}: {value}")
print()
results.append({
"filename": sub_file.name,
"decoded": True,
"match_count": len(matches),
"matches": matches
})
else:
print("❌ No devices decoded")
print()
results.append({
"filename": sub_file.name,
"decoded": False,
"reason": "Pattern decoder found no matches"
})
except Exception as e:
print(f"❌ Error: {e}")
import traceback
traceback.print_exc()
print()
results.append({
"filename": sub_file.name,
"decoded": False,
"error": str(e)
})
# Summary
print("=" * 80)
print("SUMMARY")
print("=" * 80)
print()
total_files = len(results)
success_rate = (successful_decodes / total_files * 100) if total_files > 0 else 0
print(f"Total Files: {total_files}")
print(f"Successful Decodes: {successful_decodes} ({success_rate:.1f}%)")
print(f"Failed Decodes: {total_files - successful_decodes}")
print(f"Total Devices: {total_devices_decoded}")
print()
# Detailed results
print("Results by File:")
print()
for result in results:
filename = result["filename"]
decoded = result.get("decoded", False)
match_count = result.get("match_count", 0)
if decoded:
status = f"✅ {match_count} device(s) decoded"
elif "error" in result:
status = f"❌ Error: {result['error'][:50]}"
elif "reason" in result:
status = f"⚠️ {result['reason']}"
else:
status = "❌ No decode"
print(f" {filename:<55} {status}")
print()
# Device breakdown
if successful_decodes > 0:
print("=" * 80)
print("DECODED DEVICES")
print("=" * 80)
print()
for result in results:
if result.get("decoded") and result.get("matches"):
print(f"{result['filename']}:")
for match in result['matches']:
print(f" → {match.manufacturer or 'Unknown'} {match.name} ({match.confidence:.0%})")
print()
# Analysis
print("=" * 80)
print("ANALYSIS")
print("=" * 80)
print()
if success_rate >= 50:
print("🎉 EXCELLENT! Pattern decoder is working well!")
print(f" Achieved {success_rate:.1f}% decode rate with single-transmission captures")
elif success_rate >= 25:
print("✅ GOOD! Pattern decoder is successfully identifying devices")
print(f" {success_rate:.1f}% success rate shows promise")
elif success_rate > 0:
print("⚠️ PARTIAL SUCCESS - Some devices decoded")
print(f" {success_rate:.1f}% success rate - may need tuning")
else:
print("❌ NO DECODES")
print(" Possible causes:")
print(" - Timing patterns don't match protocol database")
print(" - Signals too short for reliable analysis")
print(" - Need to tune confidence thresholds")
print()
# Comparison with RTL_433
print("Comparison with RTL_433:")
print(" RTL_433 (multi-rep decoder): 0% (0/8 files)")
print(f" Pattern Decoder (single-tx): {success_rate:.1f}% ({successful_decodes}/{total_files} files)")
print()
if successful_decodes > 0:
improvement = "SIGNIFICANT IMPROVEMENT" if success_rate > 30 else "IMPROVEMENT"
print(f" Result: {improvement} ✅")
print(f" Validates that pattern-based approach works for short captures!")
else:
print(" Result: No improvement yet (needs investigation)")
print(" Next steps:")
print(" - Check if timing thresholds are too strict")
print(" - Add more protocols to database")
print(" - Lower confidence thresholds")
print()
print("=" * 80)
return successful_decodes > 0
if __name__ == '__main__':
success = test_pattern_decoder()
sys.exit(0 if success else 1)