Files
giglez/docs/signature_databases.md

16 KiB

Signature Database Integration

Overview

This document describes how to integrate and use signature databases from Flipper Zero, RTL_433, and community sources for device identification.

1. Flipper Zero Sub-GHz Database

Source

File Format Structure

Standard Key File

Filetype: Flipper SubGhz Key File
Version: 1
Frequency: 433920000
Preset: FuriHalSubGhzPresetOok270Async
Protocol: Princeton
Bit: 24
Key: 00 00 00 00 00 95 D5 D4
TE: 400

Field Descriptions:

  • Filetype: Must be "Flipper SubGhz Key File" for protocol files
  • Version: File format version (currently 1)
  • Frequency: Operating frequency in Hz (e.g., 433920000 = 433.92 MHz)
  • Preset: Modulation preset (see Preset Types below)
  • Protocol: Protocol name (Princeton, KeeLoq, Star Line, etc.)
  • Bit: Number of bits in the transmission
  • Key: Hex-encoded data payload
  • TE: Timing element in microseconds (pulse width)

RAW Signal File

Filetype: Flipper SubGhz RAW File
Version: 1
Frequency: 433920000
Preset: FuriHalSubGhzPresetOok650Async
Protocol: RAW
RAW_Data: 29262 361 -68 2635 -66 24113 -66 11 -66 11 -132

RAW_Data Format:

  • Array of timing values in microseconds
  • Positive values = carrier ON
  • Negative values = carrier OFF
  • Must alternate between positive and negative
  • Up to 512 values per line

BinRAW File (Compressed)

Filetype: Flipper SubGhz RAW File
Version: 1
Frequency: 315000000
Preset: FuriHalSubGhzPreset2FSKDev238Async
Protocol: BinRAW
Bit: 1572
TE: 597
Bit_RAW: 260
Data_RAW: 00 00 00 00 AA AA AA AA 0F 4A B5 55

BinRAW Format:

  • Bit: Total bits in transmission
  • TE: Timing element (microsecond per bit)
  • Bit_RAW: Number of bits in compressed format
  • Data_RAW: Bit-packed data (1=carrier, 0=gap)

Preset Types

Preset Modulation Bandwidth Deviation Use Case
FuriHalSubGhzPresetOok270Async OOK 270 kHz - Standard garage doors, remotes
FuriHalSubGhzPresetOok650Async OOK 650 kHz - Fast protocols, doorbells
FuriHalSubGhzPreset2FSKDev238Async 2FSK 270 kHz 2.38 kHz TPMS, some sensors
FuriHalSubGhzPreset2FSKDev476Async 2FSK 270 kHz 47.6 kHz High-deviation FSK
FuriHalSubGhzPresetCustom Custom Varies Varies User-defined configs

Custom Presets

Custom presets allow specific CC1101 register configurations:

Filetype: Flipper SubGhz Key File
Version: 1
Frequency: 868350000
Preset: FuriHalSubGhzPresetCustom
Custom_preset_module: CC1101
Custom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 00 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 C0 00 00 00 00 00 00 00
Protocol: StarLine
...

Common Protocols

Protocol Frequency Modulation Bit Length Use Case
Princeton 433.92 MHz OOK_PWM 24 Generic remotes, garage doors
KeeLoq 433.92 MHz OOK 64-66 Car key fobs, secure remotes
Star Line 433.92 MHz OOK Varies Car alarm systems
Came 433.92 MHz OOK 12 Gate openers
Nice FLO 433.92 MHz OOK 12-24 Gate openers
Somfy Telis 433.42 MHz OOK 56 Window blinds
Holtek HT12X 433.92 MHz OOK_PWM 12 Generic remotes

Importing Flipper Signatures

Step 1: Clone the Repository

cd signatures/flipper
git clone --depth 1 https://github.com/flipperdevices/flipperzero-firmware.git temp
cp -r temp/assets/resources/subghz/assets/* ./
rm -rf temp

Step 2: Parse .sub Files

import re
from pathlib import Path

def parse_flipper_sub(file_path):
    """Parse a Flipper Zero .sub file"""
    data = {}
    with open(file_path, 'r') as f:
        for line in f:
            line = line.strip()
            if ':' in line:
                key, value = line.split(':', 1)
                data[key.strip()] = value.strip()
    return data

# Example usage
sub_file = Path('signatures/flipper/princeton_433.sub')
parsed = parse_flipper_sub(sub_file)
print(f"Protocol: {parsed.get('Protocol')}")
print(f"Frequency: {parsed.get('Frequency')} Hz")
print(f"Key: {parsed.get('Key')}")

Step 3: Import to Database

def import_flipper_signature(parsed_data, device_id):
    """Import parsed .sub file to database"""
    signature = {
        'device_id': device_id,
        'protocol': parsed_data.get('Protocol'),
        'frequency': int(parsed_data.get('Frequency', 0)),
        'modulation': parse_preset_modulation(parsed_data.get('Preset')),
        'bit_pattern': bytes.fromhex(parsed_data.get('Key', '').replace(' ', '')),
        'timing_min': int(parsed_data.get('TE', 0)) * 0.9,  # 10% tolerance
        'timing_max': int(parsed_data.get('TE', 0)) * 1.1,
        'source': 'flipper',
        'source_file': str(file_path)
    }
    # Insert into database
    db.signatures.insert(signature)

2. RTL_433 Protocol Database

Source

Protocol Structure

RTL_433 protocols are defined in C code with decoder specifications:

static char* output_fields[] = {
    "model",
    "id",
    "channel",
    "battery_ok",
    "temperature_C",
    "humidity",
    "mic",
    NULL,
};

r_device acurite_tower = {
    .name           = "Acurite-Tower",
    .modulation     = OOK_PULSE_PWM,
    .short_width    = 220,
    .long_width     = 440,
    .reset_limit    = 900,
    .decode_fn      = &acurite_tower_decode,
    .fields         = output_fields,
};

JSON Output Format

RTL_433 outputs decoded data as JSON:

{
  "time": "2025-01-11 20:15:32",
  "model": "Acurite-Tower",
  "id": 12345,
  "channel": "A",
  "battery_ok": 1,
  "temperature_C": 22.5,
  "humidity": 45,
  "mic": "CRC"
}

Common Fields

Field Type Description
time String Timestamp in ISO 8601 format
model String Manufacturer-Model identifier
id Integer Unique device ID
channel String Channel identifier (A, B, C, etc.)
battery_ok Integer Battery status (0=low, 1=ok)
temperature_C Float Temperature in Celsius
humidity Integer Relative humidity percentage
mic String Message integrity check type

Modulation Types

Modulation Description Common Devices
OOK_PWM Pulse Width Modulation Remotes, sensors
OOK_PPM Pulse Position Modulation Weather stations
OOK_PCM Pulse Code Modulation Security sensors
FSK_PCM FSK Pulse Code TPMS, smart meters
FSK_PWM FSK Pulse Width Advanced sensors

Extracting Protocol Definitions

Step 1: Extract from Source Code

cd signatures/rtl433
git clone --depth 1 https://github.com/merbanan/rtl_433.git temp
grep -r "r_device" temp/src/devices/*.c > protocols.txt
rm -rf temp

Step 2: Parse Protocol Definitions

import re
import json

def parse_rtl433_protocol(protocol_string):
    """Extract protocol definition from C code"""
    pattern = r'r_device\s+(\w+)\s*=\s*{([^}]+)}'
    match = re.search(pattern, protocol_string, re.DOTALL)

    if not match:
        return None

    name = match.group(1)
    body = match.group(2)

    # Extract fields
    modulation = re.search(r'\.modulation\s*=\s*(\w+)', body)
    short_width = re.search(r'\.short_width\s*=\s*(\d+)', body)
    long_width = re.search(r'\.long_width\s*=\s*(\d+)', body)
    model_name = re.search(r'\.name\s*=\s*"([^"]+)"', body)

    return {
        'protocol_name': name,
        'model': model_name.group(1) if model_name else None,
        'modulation': modulation.group(1) if modulation else None,
        'short_width': int(short_width.group(1)) if short_width else None,
        'long_width': int(long_width.group(1)) if long_width else None
    }

Step 3: Generate Protocol Database

# Read all protocol definitions
protocols = []
for c_file in Path('temp/src/devices').glob('*.c'):
    with open(c_file, 'r') as f:
        content = f.read()
        parsed = parse_rtl433_protocol(content)
        if parsed:
            protocols.append(parsed)

# Save as JSON
with open('signatures/rtl433/protocols.json', 'w') as f:
    json.dump(protocols, f, indent=2)

Using rtl_433 Test Data

RTL_433 provides test signals with expected JSON output:

# Clone test repository
git clone https://github.com/merbanan/rtl_433_tests.git signatures/rtl433/tests

# Test files are organized as:
# rtl_433_tests/tests/{protocol_name}/{signal_type}/*.cu8
# rtl_433_tests/tests/{protocol_name}/{signal_type}/*.json

Example test data structure:

rtl_433_tests/tests/acurite/01/
├── g001_433.92M_250k.cu8  # Raw signal capture
└── g001_433.92M_250k.json # Expected decoded output

Importing RTL_433 Signatures

def import_rtl433_protocol(protocol_data, device_id):
    """Import RTL_433 protocol to database"""
    # Determine frequency from modulation
    frequency_map = {
        'OOK': 433920000,
        'FSK': 868000000  # Varies by device
    }

    modulation_type = protocol_data.get('modulation', 'OOK')
    base_freq = frequency_map.get(modulation_type[:3], 433920000)

    signature = {
        'device_id': device_id,
        'protocol': protocol_data.get('model'),
        'frequency': base_freq,
        'modulation': modulation_type,
        'timing_min': protocol_data.get('short_width'),
        'timing_max': protocol_data.get('long_width'),
        'source': 'rtl433',
        'source_file': protocol_data.get('source_file')
    }

    # Insert into rtl433_protocols table
    db.rtl433_protocols.insert({
        'protocol_name': protocol_data.get('protocol_name'),
        'model': protocol_data.get('model'),
        'modulation': modulation_type,
        'short_width': protocol_data.get('short_width'),
        'long_width': protocol_data.get('long_width'),
        'json_fields': protocol_data.get('fields', {})
    })

    # Also create general signature
    db.signatures.insert(signature)

3. Universal Radio Hacker (URH)

Source

Signal File Formats

.complex Files

Raw I/Q signal data in binary format:

  • Interleaved I and Q samples
  • Typically 32-bit float or 8-bit integer
  • Sample rate metadata in accompanying .cfl file

.urh Project Files

XML-based project containing:

  • Signal definitions
  • Demodulation parameters
  • Protocol structure
  • Labeled message fields

Extracting URH Protocols

URH projects often contain valuable protocol information in the community wiki.

# Download community-shared URH projects
cd signatures/urh
# Check URH GitHub wiki for shared project links

4. Community Signatures

User Submission Format

When users identify a device, they can submit:

{
  "device": {
    "manufacturer": "Chamberlain",
    "model": "KLIK3U-SS",
    "type": "garage_door_opener",
    "fcc_id": "K49KLIK3U"
  },
  "signature": {
    "frequency": 433920000,
    "protocol": "Security+ 2.0",
    "modulation": "OOK",
    "notes": "Rolling code, 3-button remote"
  },
  "evidence": {
    "photos": ["device_front.jpg", "device_back.jpg", "fcc_label.jpg"],
    "capture_file": "chamberlain_klik3u_001.sub"
  },
  "location": {
    "latitude": 40.7128,
    "longitude": -74.0060,
    "accuracy": 10
  }
}

Verification Process

  1. User submits identification with photo evidence
  2. Community members vote (upvote/downvote)
  3. After 5 net upvotes, identification is auto-verified
  4. Verified identifications create new signature entries
  5. High-reputation users can verify immediately

5. Signature Matching Algorithm

Matching Strategy

def match_capture_to_signatures(capture):
    """
    Match a capture against known signatures
    Returns list of (device_id, confidence) tuples
    """
    matches = []

    # Exact protocol + frequency + timing match
    exact = db.query('''
        SELECT device_id, 1.0 as confidence
        FROM signatures
        WHERE protocol = ? AND frequency = ? AND ? BETWEEN timing_min AND timing_max
    ''', [capture.protocol, capture.frequency, capture.timing_element])
    matches.extend(exact)

    # Protocol + frequency match (80% confidence)
    protocol_freq = db.query('''
        SELECT device_id, 0.8 as confidence
        FROM signatures
        WHERE protocol = ? AND frequency = ?
    ''', [capture.protocol, capture.frequency])
    matches.extend(protocol_freq)

    # Bit pattern matching (if available)
    if capture.key_data:
        pattern_matches = match_bit_pattern(capture.key_data)
        matches.extend(pattern_matches)

    # De-duplicate and sort by confidence
    unique_matches = {}
    for device_id, conf in matches:
        if device_id not in unique_matches or conf > unique_matches[device_id]:
            unique_matches[device_id] = conf

    return sorted(unique_matches.items(), key=lambda x: x[1], reverse=True)

Bit Pattern Matching

def match_bit_pattern(key_data, signatures):
    """
    Match key data against signature patterns with masks
    """
    matches = []

    for sig in signatures:
        if not sig.bit_mask:
            continue

        # Apply mask and compare
        masked_capture = apply_mask(key_data, sig.bit_mask)
        masked_signature = apply_mask(sig.bit_pattern, sig.bit_mask)

        if masked_capture == masked_signature:
            confidence = 0.9 * sig.weight
            matches.append((sig.device_id, confidence))

    return matches

def apply_mask(data, mask):
    """Bitwise AND operation on byte arrays"""
    return bytes(a & b for a, b in zip(data, mask))

6. Database Import Scripts

Complete Import Pipeline

#!/usr/bin/env python3
"""Import all signature databases"""

import sys
from pathlib import Path
from importers import flipper, rtl433, urh

def main():
    print("Starting signature database import...")

    # Import Flipper Zero signatures
    print("\n[1/3] Importing Flipper Zero .sub files...")
    flipper_dir = Path('signatures/flipper')
    flipper_count = flipper.import_all(flipper_dir)
    print(f"  Imported {flipper_count} Flipper signatures")

    # Import RTL_433 protocols
    print("\n[2/3] Importing RTL_433 protocols...")
    rtl433_file = Path('signatures/rtl433/protocols.json')
    rtl433_count = rtl433.import_protocols(rtl433_file)
    print(f"  Imported {rtl433_count} RTL_433 protocols")

    # Import URH community signals
    print("\n[3/3] Importing URH signals...")
    urh_dir = Path('signatures/urh')
    urh_count = urh.import_all(urh_dir)
    print(f"  Imported {urh_count} URH signatures")

    print(f"\nTotal signatures imported: {flipper_count + rtl433_count + urh_count}")

if __name__ == '__main__':
    main()

7. Signature Database Maintenance

Regular Updates

#!/bin/bash
# scripts/update_signatures.sh

cd signatures/flipper
git pull

cd ../rtl433
git pull

# Re-import updated signatures
python3 scripts/import_signatures.py --update

Quality Metrics

Track signature effectiveness:

-- Signature match success rate
SELECT
    s.id,
    d.manufacturer,
    d.model,
    COUNT(c.id) as total_matches,
    AVG(c.match_confidence) as avg_confidence
FROM signatures s
JOIN devices d ON s.device_id = d.id
LEFT JOIN captures c ON c.device_id = d.id AND c.match_method = 'auto'
GROUP BY s.id, d.manufacturer, d.model
ORDER BY total_matches DESC;