01b06fadc6
Completed JavaScript port of pattern_decoder.py with full client-side
RF signal analysis capability for Sub-GHz devices.
## New Files Created (8 files, ~2,100 lines):
### Core Decoder Modules:
- static/js/decoder/protocol-database.js (410 lines)
* 18 RF protocol signatures (Weather, Garage, TPMS, Doorbells, Security, Remotes)
* ProtocolSignature class with timing/frequency matching
* ProtocolDatabase with indexed queries
* Categories: Weather Sensors (7), Garage Doors (3), TPMS (2), etc.
- static/js/decoder/pulse-analyzer.js (238 lines)
* K-means clustering for SHORT/LONG pulse detection
* identifyPulseWidths() using k-means
* decodeToBits() for PWM encoding (SHORT=0, LONG=1)
* analyzePulseTrain() for comprehensive timing analysis
* validatePulseData() quality checks
* estimateSNR() signal quality estimation
- static/js/decoder/fingerprint.js (312 lines)
* PulseFingerprint class for statistical characteristics
* extractFingerprint() - mean, std, duty cycle, pulse/gap ratio
* calculateSimilarity() - 0-1 similarity score with weights
* compareToProtocol() - protocol library matching
* classifySignal() - heuristic device type classification
* generateReport() - debugging output
- static/js/decoder/pattern-decoder.js (457 lines)
* DeviceMatch class (standardized result format)
* PatternDecoder main decoder class
* Multi-strategy decoding:
- Strategy 1: Timing pattern analysis (K-means + protocol DB)
- Strategy 2: Statistical fingerprint matching
- Strategy 3: Heuristic classification (fallback)
* Match deduplication and ranking
* Statistics tracking (success rate, avg confidence)
- static/js/decoder/index.js (226 lines)
* ES6 module exports for all decoder components
* parseSubFile() - Flipper Zero .sub parser
* quickDecode() - convenience API
* decodeFromURL() - fetch and decode remote files
* decodeFromFile() - browser File API support
* getVersion() - version and feature info
### Demo & Testing:
- static/decoder-demo.html (374 lines)
* Beautiful drag-and-drop UI for .sub file upload
* Real-time client-side decoding (no server needed!)
* Interactive results with confidence badges
* Signal quality visualization
* Debug console output
* Mobile-responsive design
- static/js/decoder/test.js (120 lines)
* Node.js test suite for decoder
* Tests all 4 strategies (timing, fingerprint, protocol DB, heuristics)
* Validates parsing, analysis, and matching
* Example output shows 7 matches @ 43.3% confidence
- package.json
* Enable ES6 modules ("type": "module")
* NPM script: "test:decoder"
## Test Results:
✅ JavaScript decoder successfully tested with Node.js
✅ Found 7 device matches on test .sub file (43.3% best confidence)
✅ All modules working: protocol DB (18 protocols), pulse analysis, fingerprinting, decoding
✅ Browser demo ready at /static/decoder-demo.html
## Features:
- 🔬 K-means pulse width clustering
- 📊 Statistical fingerprinting
- 📚 Protocol library matching (18 protocols)
- 🎯 Multi-strategy matching
- 📱 Client-side decoding (privacy-focused)
- 🌐 Browser and Node.js compatible
- ⚡ Single-transmission decoding (no repetitions needed)
## Next Steps:
- Integrate decoder into main upload UI
- Test with T-Embed and Flipper datasets
- Optimize for larger .sub files
- Add more protocol signatures
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
227 lines
6.4 KiB
JavaScript
227 lines
6.4 KiB
JavaScript
/**
|
|
* GigLez Pattern Decoder - Main Entry Point
|
|
* JavaScript RF Signal Decoder for Sub-GHz Devices
|
|
*
|
|
* Export all decoder modules for browser and Node.js usage
|
|
*
|
|
* @module giglez-decoder
|
|
* @version 1.0.0
|
|
*/
|
|
|
|
// ============================================================================
|
|
// Protocol Database
|
|
// ============================================================================
|
|
export {
|
|
ProtocolSignature,
|
|
ProtocolDatabase,
|
|
WEATHER_SENSORS,
|
|
GARAGE_DOOR_OPENERS,
|
|
DOORBELLS,
|
|
TIRE_PRESSURE,
|
|
SECURITY_SENSORS,
|
|
REMOTE_CONTROLS,
|
|
ALL_PROTOCOLS,
|
|
protocolDb
|
|
} from './protocol-database.js';
|
|
|
|
// ============================================================================
|
|
// Pulse Analysis
|
|
// ============================================================================
|
|
export {
|
|
kMeans,
|
|
identifyPulseWidths,
|
|
decodeToBits,
|
|
analyzePulseTrain,
|
|
validatePulseData,
|
|
estimateSNR
|
|
} from './pulse-analyzer.js';
|
|
|
|
// ============================================================================
|
|
// Statistical Fingerprinting
|
|
// ============================================================================
|
|
export {
|
|
PulseFingerprint,
|
|
extractFingerprint,
|
|
calculateSimilarity,
|
|
compareToProtocol,
|
|
classifySignal,
|
|
generateReport
|
|
} from './fingerprint.js';
|
|
|
|
// ============================================================================
|
|
// Pattern Decoder (Main)
|
|
// ============================================================================
|
|
export {
|
|
DeviceMatch,
|
|
PatternDecoder,
|
|
decode
|
|
} from './pattern-decoder.js';
|
|
|
|
// ============================================================================
|
|
// Convenience API
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Quick decode function - analyze .sub file and return device matches
|
|
*
|
|
* @param {Object} metadata - Parsed .sub file metadata
|
|
* @param {number} metadata.frequency - Frequency in Hz
|
|
* @param {string} metadata.preset - Modulation preset
|
|
* @param {string} metadata.protocol - Protocol name
|
|
* @param {number[]} metadata.raw_data - Pulse timing array
|
|
* @param {Object} options - Decoder options
|
|
* @param {number} options.minConfidence - Minimum confidence threshold (default: 0.4)
|
|
* @param {number} options.maxResults - Maximum results to return (default: 10)
|
|
* @param {boolean} options.debug - Enable debug logging (default: false)
|
|
* @returns {Promise<Array>} Array of DeviceMatch objects
|
|
*
|
|
* @example
|
|
* import { quickDecode } from './decoder/index.js';
|
|
*
|
|
* const metadata = {
|
|
* frequency: 433920000,
|
|
* preset: 'FuriHalSubGhzPresetOok270Async',
|
|
* protocol: 'RAW',
|
|
* raw_data: [2980, -240, 520, -980, 520, -980, ...]
|
|
* };
|
|
*
|
|
* const matches = await quickDecode(metadata, { debug: true });
|
|
* console.log(matches[0].toString());
|
|
*/
|
|
export async function quickDecode(metadata, options = {}) {
|
|
const { PatternDecoder } = await import('./pattern-decoder.js');
|
|
const decoder = new PatternDecoder(options);
|
|
return decoder.decode(metadata);
|
|
}
|
|
|
|
/**
|
|
* Parse Flipper Zero .sub file format
|
|
*
|
|
* @param {string} content - Raw .sub file content
|
|
* @returns {Object} Parsed metadata
|
|
*
|
|
* @example
|
|
* const content = await fetch('capture.sub').then(r => r.text());
|
|
* const metadata = parseSubFile(content);
|
|
* const matches = await quickDecode(metadata);
|
|
*/
|
|
export function parseSubFile(content) {
|
|
const lines = content.split('\n');
|
|
const metadata = {
|
|
filetype: null,
|
|
version: null,
|
|
frequency: null,
|
|
preset: null,
|
|
protocol: null,
|
|
bit: null,
|
|
key: null,
|
|
te: null,
|
|
raw_data: []
|
|
};
|
|
|
|
for (const line of lines) {
|
|
const trimmed = line.trim();
|
|
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
|
|
if (trimmed.includes(':')) {
|
|
const [key, value] = trimmed.split(':', 2).map(s => s.trim());
|
|
|
|
switch (key) {
|
|
case 'Filetype':
|
|
metadata.filetype = value;
|
|
break;
|
|
case 'Version':
|
|
metadata.version = parseInt(value);
|
|
break;
|
|
case 'Frequency':
|
|
metadata.frequency = parseInt(value);
|
|
break;
|
|
case 'Preset':
|
|
metadata.preset = value;
|
|
break;
|
|
case 'Protocol':
|
|
metadata.protocol = value;
|
|
break;
|
|
case 'Bit':
|
|
metadata.bit = parseInt(value);
|
|
break;
|
|
case 'Key':
|
|
metadata.key = value;
|
|
break;
|
|
case 'TE':
|
|
metadata.te = parseInt(value);
|
|
break;
|
|
case 'RAW_Data':
|
|
// Parse pulse timing array
|
|
const pulses = value.split(/\s+/)
|
|
.map(s => parseInt(s))
|
|
.filter(n => !isNaN(n));
|
|
metadata.raw_data.push(...pulses);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return metadata;
|
|
}
|
|
|
|
/**
|
|
* Decode .sub file from URL
|
|
*
|
|
* @param {string} url - URL to .sub file
|
|
* @param {Object} options - Decoder options
|
|
* @returns {Promise<Array>} Device matches
|
|
*
|
|
* @example
|
|
* const matches = await decodeFromURL('https://example.com/capture.sub');
|
|
* console.log(`Found ${matches.length} potential devices`);
|
|
*/
|
|
export async function decodeFromURL(url, options = {}) {
|
|
const response = await fetch(url);
|
|
const content = await response.text();
|
|
const metadata = parseSubFile(content);
|
|
return quickDecode(metadata, options);
|
|
}
|
|
|
|
/**
|
|
* Decode .sub file from File object (browser)
|
|
*
|
|
* @param {File} file - File object from file input
|
|
* @param {Object} options - Decoder options
|
|
* @returns {Promise<Array>} Device matches
|
|
*
|
|
* @example
|
|
* document.getElementById('fileInput').addEventListener('change', async (e) => {
|
|
* const file = e.target.files[0];
|
|
* const matches = await decodeFromFile(file, { debug: true });
|
|
* console.log(matches);
|
|
* });
|
|
*/
|
|
export async function decodeFromFile(file, options = {}) {
|
|
const content = await file.text();
|
|
const metadata = parseSubFile(content);
|
|
return quickDecode(metadata, options);
|
|
}
|
|
|
|
/**
|
|
* Get decoder version information
|
|
* @returns {Object} Version info
|
|
*/
|
|
export async function getVersion() {
|
|
const { ALL_PROTOCOLS } = await import('./protocol-database.js');
|
|
return {
|
|
version: '1.0.0',
|
|
name: 'GigLez Pattern Decoder',
|
|
description: 'JavaScript RF Signal Decoder for Sub-GHz Devices',
|
|
protocols: ALL_PROTOCOLS.length,
|
|
features: [
|
|
'K-means pulse width clustering',
|
|
'Statistical fingerprinting',
|
|
'Protocol library matching',
|
|
'18 built-in protocols',
|
|
'Single-transmission decoding',
|
|
'Browser and Node.js compatible'
|
|
]
|
|
};
|
|
}
|