feat: robust timing analyzer for RF device identification - iteration 2/5
- Implemented multi-method timing extraction (K-means, histogram, percentile) - Added intelligent outlier removal with IQR method (2.5x threshold) - Integrated timing analyzer into pattern_decoder.py - Created comprehensive scoring system against protocol signatures - Handles noisy/imperfect captures with tolerance windows Components: - RobustTimingAnalyzer: Multi-strategy timing extraction - TimingCharacteristics: Extracted pulse/gap/ratio data - TimingScore: Similarity scoring with weighted components - TimingMatchStrategy: Integration with engine.py Features: - Separates HIGH/LOW pulses before outlier removal (preserves alternating pattern) - Multi-method ensemble: tries K-means → histogram → percentile - Confidence scoring based on clustering quality - Timing ratios (short/long pulse ratios) for better matching - Duty cycle calculation - Configurable tolerance windows (default ±25%) Test Coverage: - 15 new unit tests in tests/unit/test_timing_analyzer.py - All 41 tests passing (26 original + 15 new) - Coverage: clean signals, noisy signals, outliers, multi-level, scoring, real-world LaCrosse Expected Impact: - Improved single-transmission accuracy (20% → 55% projected) - Better noise tolerance for Flipper Zero captures - More accurate protocol matching with 299 signatures
This commit is contained in:
@@ -20,6 +20,7 @@ from src.matcher.protocol_database import (
|
||||
ProtocolDatabase,
|
||||
get_protocol_database
|
||||
)
|
||||
from src.matcher.timing_analyzer import get_timing_analyzer
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -82,6 +83,7 @@ class PatternDecoder:
|
||||
|
||||
def __init__(self, protocol_db: Optional[ProtocolDatabase] = None):
|
||||
self.protocol_db = protocol_db or get_protocol_database()
|
||||
self.timing_analyzer = get_timing_analyzer()
|
||||
|
||||
def decode(self, metadata: SignalMetadata) -> List[DeviceMatch]:
|
||||
"""
|
||||
@@ -118,7 +120,7 @@ class PatternDecoder:
|
||||
|
||||
def _identify_pulse_widths(self, pulses: List[int]) -> Tuple[int, int, int, int]:
|
||||
"""
|
||||
Identify SHORT/LONG pulse and gap durations using K-means clustering
|
||||
Identify SHORT/LONG pulse and gap durations using robust timing analyzer
|
||||
|
||||
Returns:
|
||||
(short_pulse, long_pulse, short_gap, long_gap) in microseconds
|
||||
@@ -126,17 +128,15 @@ class PatternDecoder:
|
||||
if not pulses:
|
||||
return (0, 0, 0, 0)
|
||||
|
||||
# Separate positive (HIGH pulses) and negative (LOW gaps)
|
||||
high_pulses = [abs(p) for p in pulses if p > 0]
|
||||
low_pulses = [abs(p) for p in pulses if p < 0]
|
||||
# Use robust timing analyzer for improved extraction
|
||||
timing = self.timing_analyzer.extract_timing(pulses)
|
||||
|
||||
# Cluster high pulses into SHORT/LONG
|
||||
short_pulse, long_pulse = self._cluster_durations(high_pulses)
|
||||
|
||||
# Cluster low pulses into SHORT/LONG
|
||||
short_gap, long_gap = self._cluster_durations(low_pulses)
|
||||
|
||||
return (short_pulse, long_pulse, short_gap, long_gap)
|
||||
return (
|
||||
timing.short_pulse_us,
|
||||
timing.long_pulse_us,
|
||||
timing.short_gap_us,
|
||||
timing.long_gap_us
|
||||
)
|
||||
|
||||
def _cluster_durations(self, durations: List[int]) -> Tuple[int, int]:
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user