Phase 3: JavaScript pattern decoder implementation
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>
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
/**
|
||||
* 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'
|
||||
]
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user