diff --git a/src/matcher/pattern_decoder.py b/src/matcher/pattern_decoder.py index b4d0869..0665825 100644 --- a/src/matcher/pattern_decoder.py +++ b/src/matcher/pattern_decoder.py @@ -146,10 +146,41 @@ class PatternDecoder: Identify a decoded (KEY) .sub file from its Protocol name. KEY files have no RAW pulses but carry a decoded `Protocol:` name, - frequency and key. We route the protocol name to a device category and - emit a match surfacing the real Flipper protocol + routed category, so - decoded uploads are no longer silently unidentifiable. + frequency and key. Two cases: + + 1. The named protocol is in our signature database. This is the + strongest possible identification — the capture device already + demodulated and *named* the protocol, and we recognise it — so we + surface the REAL catalog signature (true manufacturer + category) + with high, frequency-aware confidence. This is an exact "database + match", not a timing guess, and it keeps the surfaced category + consistent with the catalog (e.g. Princeton -> Garage Door Opener) + instead of the router's coarser guess. + + 2. The name is unknown to us. Fall back to category routing so the + upload is still identified by family rather than silently dropped. """ + known = self._lookup_known_protocol(metadata.protocol, metadata.frequency) + if known is not None: + freq_ok = bool(metadata.frequency) and known.matches_frequency(metadata.frequency) + # Exact, already-decoded match against a known protocol. Confidence + # is high but never a literal 1.0 (the key payload itself is not + # cryptographically verified); a consistent frequency lifts it. + confidence = 0.95 if freq_ok else 0.90 + details = { + "predicted_category": known.category, + "source": "decoded_key_exact", + "bit_length": metadata.bit_length, + "frequency_match": freq_ok, + "manufacturer": known.manufacturer, + } + return [DeviceMatch( + protocol=known, + confidence=confidence, + match_method="decoded_key_exact", + details=details, + )] + pred = self.category_router.route_by_protocol( metadata.protocol, metadata.frequency ) @@ -172,6 +203,30 @@ class PatternDecoder: details=details, )] + def _lookup_known_protocol( + self, name: Optional[str], frequency: Optional[int] + ) -> Optional[ProtocolSignature]: + """Exact (case-insensitive) protocol-name lookup in the signature DB. + + When several catalog entries share a name, prefer one whose frequency + band matches the capture; otherwise return the first. Returns None if + the name is unknown. + """ + if not name: + return None + target = name.strip().lower() + candidates = [ + p for p in self.protocol_db.get_all() + if p.name.strip().lower() == target + ] + if not candidates: + return None + if frequency: + for cand in candidates: + if cand.matches_frequency(frequency): + return cand + return candidates[0] + def _identify_pulse_widths(self, pulses: List[int]) -> Tuple[int, int, int, int]: """ Identify SHORT/LONG pulse and gap durations using robust timing analyzer