Sub-GHz#
The Sub-GHz module provides functionality for transmitting and receiving signals on sub-gigahertz radio frequencies, commonly used for IoT devices, remote controls, and various wireless protocols.
Functions#
subghz.transmitFile(filePath)#
Transmits a Sub-GHz signal from a file containing the signal data.
Parameters:
| Parameter | Type | Description |
|---|---|---|
filePath |
string |
Path to the file containing Sub-GHz signal data |
Returns: boolean - True if transmission was successful, false otherwise
Example:
subghz.transmit(frequency, data)#
Transmits raw Sub-GHz data on a specified frequency.
Parameters:
| Parameter | Type | Description |
|---|---|---|
frequency |
number |
Frequency in Hz (e.g., 433920000 for 433.92 MHz) |
data |
string |
Raw signal data in appropriate format |
Returns: boolean - True if transmission was successful, false otherwise
Example:
Sub-GHz Applications#
Remote Control Cloner#
var subghz = require("subghz");
var dialog = require("dialog");
var keyboard = require("keyboard");
var display = require("display");
var storage = require("storage");
var display = require("display");
function remoteControlCloner() {
while (true) {
const choice = dialog.choice([
"Scan for Signals",
"Transmit Signal",
"Signal Library",
"Raw Transmitter",
"Frequency Scanner",
"Exit"
]);
switch (choice) {
case 0: // Scan for Signals
scanForSignals();
break;
case 1: // Transmit Signal
transmitSavedSignal();
break;
case 2: // Signal Library
manageSignalLibrary();
break;
case 3: // Raw Transmitter
rawTransmitter();
break;
case 4: // Frequency Scanner
frequencyScanner();
break;
case 5: // Exit
return;
}
}
}
function scanForSignals() {
const frequencies = [
{ name: "315 MHz", freq: 315000000 },
{ name: "433.92 MHz", freq: 433920000 },
{ name: "868.35 MHz", freq: 868350000 },
{ name: "915 MHz", freq: 915000000 }
];
const choice = dialog.choice(frequencies.map(f => f.name).concat(["Cancel"]));
if (choice >= 0 && choice < frequencies.length) {
const selectedFreq = frequencies[choice];
dialog.info(`Scanning ${selectedFreq.name}\\nPress any button to stop`);
display.fill(0);
display.print(0, 0, "Scanning...", 2);
display.print(0, 20, selectedFreq.name, 1);
display.print(0, 35, "Press Back to stop", 1);
// Simulate signal scanning (would use actual receiver in real implementation)
let scanTime = 0;
const maxScanTime = 30000; // 30 seconds
while (scanTime < maxScanTime) {
const key = keyboard.getKey();
if (keyboard.getEscPress()) break;
// Simulate signal detection
if (Math.random() > 0.99) { // 1% chance per iteration
const signalData = captureSignal(selectedFreq.freq);
if (signalData) {
handleCapturedSignal(signalData, selectedFreq);
return;
}
}
// Update display
var dots = "";
for (var d = 0; d < (scanTime / 500) % 4; d++) {
dots += ".";
}
display.drawFillRect(0, 50, 128, 10, 0);
display.print(0, 50, "Scanning" + dots, 1);
delay(100);
scanTime += 100;
}
dialog.info("Scan completed - no signals detected");
}
}
function captureSignal(frequency) {
// Simulate signal capture (would use actual receiver)
var simulatedSignal = {
frequency: frequency,
protocol: detectProtocol(),
rawData: generateRandomSignal(),
timestamp: Date.now(),
rssi: -50 - Math.random() * 30 // Simulate signal strength
};
return simulatedSignal;
}
function detectProtocol() {
const protocols = ["AM650", "AM270", "FM238", "FM476", "Unknown"];
return protocols[Math.floor(Math.random() * protocols.length)];
}
function generateRandomSignal() {
const patterns = [];
for (let i = 0; i < 50; i++) {
const high = 200 + Math.random() * 1000;
const low = -(200 + Math.random() * 1000);
patterns.push(Math.round(high), Math.round(low));
}
return "RAW_Data: " + patterns.join(" ");
}
function handleCapturedSignal(signal, frequency) {
var signalInfo = "Signal Detected!\n\nFrequency: " + frequency.name + "\nProtocol: " + signal.protocol + "\nRSSI: " + signal.rssi.toFixed(1) + " dBm\nTimestamp: " + new Date(signal.timestamp).toLocaleString() + "\n\nRaw Data Preview:\n" + signal.rawData.substring(0, 100) + "...";
dialog.viewText(signalInfo, "Captured Signal");
const action = dialog.choice([
"Save Signal",
"Transmit Now",
"Analyze",
"Discard"
]);
switch (action) {
case 0: // Save Signal
saveSignal(signal, frequency);
break;
case 1: // Transmit Now
if (subghz.transmit(signal.frequency, signal.rawData)) {
dialog.success("Signal retransmitted!");
} else {
dialog.error("Retransmission failed");
}
break;
case 2: // Analyze
analyzeSignal(signal);
break;
// case 3: Discard - do nothing
}
}
function saveSignal(signal, frequency) {
const name = keyboard.keyboard("Signal name:", `signal_${frequency.name.replace(/\\s+/g, '_')}`);
if (!name) return;
let filename = name;
if (!filename.endsWith('.sub')) {
filename += '.sub';
}
const subghzFormat = `Filetype: Flipper SubGhz RAW File
Version: 1
Frequency: ${signal.frequency}
Preset: FuriHalSubGhzPresetOok650Async
Protocol: ${signal.protocol}
Bit: 0
Key: 00 00 00 00 00 00 00 00
TE: 500
${signal.rawData}`;
const filepath = `/sd/subghz/${filename}`;
if (storage.write(filepath, subghzFormat)) {
dialog.success(`Signal saved as: ${filename}`);
// Add to signal library index
addToSignalLibrary(filepath, {
name: name,
frequency: frequency.name,
protocol: signal.protocol,
timestamp: signal.timestamp
});
} else {
dialog.error("Failed to save signal");
}
}
function addToSignalLibrary(filepath, metadata) {
let library = [];
try {
const libraryData = storage.read("/sd/subghz/library.json");
if (libraryData) {
library = JSON.parse(libraryData);
}
} catch (e) {
// Start with empty library
}
library.push({
filepath: filepath,
metadata: metadata
});
try {
storage.write("/sd/subghz/library.json", JSON.stringify(library, null, 2));
} catch (e) {
console.log("Failed to update signal library");
}
}
Signal Library Management#
function manageSignalLibrary() {
let library = loadSignalLibrary();
while (true) {
if (library.length === 0) {
dialog.info("Signal library is empty");
return;
}
const choices = library.map(entry => {
const name = entry.metadata.name;
const freq = entry.metadata.frequency;
const protocol = entry.metadata.protocol;
return `${name} (${freq}, ${protocol})`;
});
choices.push("Refresh Library");
choices.push("Clear Library");
choices.push("< Back");
const choice = dialog.choice(choices);
if (choice >= 0 && choice < library.length) {
// Signal selected
manageSignalEntry(library[choice]);
library = loadSignalLibrary(); // Refresh
} else if (choice === library.length) {
// Refresh
library = loadSignalLibrary();
} else if (choice === library.length + 1) {
// Clear Library
if (dialog.choice(["Cancel", "Clear All"]) === 1) {
clearSignalLibrary();
library = [];
}
} else {
// Back
return;
}
}
}
function loadSignalLibrary() {
try {
const libraryData = storage.read("/sd/subghz/library.json");
if (libraryData) {
return JSON.parse(libraryData);
}
} catch (e) {
console.log("Failed to load signal library");
}
return [];
}
function manageSignalEntry(entry) {
const info = `Signal: ${entry.metadata.name}
File: ${entry.filepath}
Frequency: ${entry.metadata.frequency}
Protocol: ${entry.metadata.protocol}
Saved: ${new Date(entry.metadata.timestamp).toLocaleString()}`;
dialog.viewText(info, "Signal Details");
const action = dialog.choice([
"Transmit",
"Edit Name",
"Test Transmission",
"View Raw Data",
"Delete",
"Cancel"
]);
switch (action) {
case 0: // Transmit
transmitSignalFile(entry.filepath);
break;
case 1: // Edit Name
editSignalName(entry);
break;
case 2: // Test Transmission
testSignalTransmission(entry);
break;
case 3: // View Raw Data
viewSignalRawData(entry.filepath);
break;
case 4: // Delete
deleteSignal(entry);
break;
// case 5: Cancel - do nothing
}
}
function transmitSignalFile(filepath) {
if (subghz.transmitFile(filepath)) {
dialog.success("Signal transmitted successfully!");
} else {
dialog.error("Failed to transmit signal");
}
}
function editSignalName(entry) {
const newName = keyboard.keyboard("New name:", entry.metadata.name);
if (newName && newName !== entry.metadata.name) {
entry.metadata.name = newName;
saveSignalLibrary();
dialog.success("Signal renamed");
}
}
function testSignalTransmission(entry) {
const count = parseInt(keyboard.numKeyboard("Number of transmissions:", "3"));
const delay_ms = parseInt(keyboard.numKeyboard("Delay between (ms):", "1000"));
if (isNaN(count) || isNaN(delay_ms) || count < 1) {
dialog.error("Invalid parameters");
return;
}
let successful = 0;
for (let i = 1; i <= count; i++) {
display.fill(0);
display.print(0, 0, "Test Transmission", 2);
display.print(0, 25, `Attempt ${i}/${count}`, 1);
display.print(0, 40, entry.metadata.name, 1);
if (subghz.transmitFile(entry.filepath)) {
successful++;
console.log(`Test ${i}/${count}: SUCCESS`);
} else {
console.log(`Test ${i}/${count}: FAILED`);
}
if (i < count) {
delay(delay_ms);
}
}
dialog.info(`Test completed\\nSuccessful: ${successful}/${count}`);
}
function viewSignalRawData(filepath) {
try {
const data = storage.read(filepath);
if (data) {
// Extract just the raw data portion for viewing
const lines = data.split('\\n');
let rawData = "";
let inRawSection = false;
for (let line of lines) {
if (line.startsWith('RAW_Data:')) {
inRawSection = true;
}
if (inRawSection) {
rawData += line + "\\n";
}
}
if (rawData) {
dialog.viewText(rawData, "Raw Signal Data");
} else {
dialog.error("No raw data found in signal file");
}
} else {
dialog.error("Failed to read signal file");
}
} catch (e) {
dialog.error(`Error reading file: ${e.message}`);
}
}
function deleteSignal(entry) {
const confirm = dialog.choice([
"Cancel",
`Delete "${entry.metadata.name}"`
]);
if (confirm === 1) {
try {
// Remove file
storage.remove(entry.filepath);
// Remove from library
let library = loadSignalLibrary();
library = library.filter(e => e.filepath !== entry.filepath);
storage.write("/sd/subghz/library.json", JSON.stringify(library, null, 2));
dialog.success("Signal deleted");
} catch (e) {
dialog.error(`Failed to delete signal: ${e.message}`);
}
}
}
Raw Signal Transmitter#
function rawTransmitter() {
while (true) {
const choice = dialog.choice([
"Manual Raw Data",
"Pattern Generator",
"Frequency Sweep",
"Pulse Train",
"Modulation Test",
"Exit"
]);
switch (choice) {
case 0: // Manual Raw Data
manualRawTransmission();
break;
case 1: // Pattern Generator
patternGenerator();
break;
case 2: // Frequency Sweep
frequencySweep();
break;
case 3: // Pulse Train
pulseTrainGenerator();
break;
case 4: // Modulation Test
modulationTest();
break;
case 5: // Exit
return;
}
}
}
function manualRawTransmission() {
const freq = parseInt(keyboard.numKeyboard("Frequency (Hz):", "433920000"));
if (isNaN(freq) || freq < 300000000 || freq > 1000000000) {
dialog.error("Invalid frequency (300-1000 MHz)");
return;
}
const rawData = keyboard.textArea("Raw data:", "RAW_Data: 1000 -1000 500 -500");
if (!rawData || !rawData.startsWith("RAW_Data:")) {
dialog.error("Invalid raw data format");
return;
}
if (subghz.transmit(freq, rawData)) {
dialog.success(`Transmitted on ${(freq / 1000000).toFixed(2)} MHz`);
} else {
dialog.error("Transmission failed");
}
}
function patternGenerator() {
const patterns = [
{ name: "Square Wave", generator: generateSquareWave },
{ name: "Pulse Train", generator: generatePulseTrain },
{ name: "Random Pattern", generator: generateRandomPattern },
{ name: "Morse Code", generator: generateMorsePattern }
];
const patternChoice = dialog.choice(patterns.map(p => p.name).concat(["Cancel"]));
if (patternChoice >= 0 && patternChoice < patterns.length) {
const pattern = patterns[patternChoice];
const freq = parseInt(keyboard.numKeyboard("Frequency (Hz):", "433920000"));
if (isNaN(freq)) {
dialog.error("Invalid frequency");
return;
}
const rawData = pattern.generator();
if (rawData) {
console.log(`Generated ${pattern.name}:`);
console.log(rawData.substring(0, 100) + "...");
if (subghz.transmit(freq, rawData)) {
dialog.success(`${pattern.name} transmitted`);
} else {
dialog.error("Transmission failed");
}
}
}
}
function generateSquareWave() {
const highTime = parseInt(keyboard.numKeyboard("High time (μs):", "1000"));
const lowTime = parseInt(keyboard.numKeyboard("Low time (μs):", "1000"));
const cycles = parseInt(keyboard.numKeyboard("Number of cycles:", "10"));
if (isNaN(highTime) || isNaN(lowTime) || isNaN(cycles)) {
dialog.error("Invalid parameters");
return null;
}
const pattern = [];
for (let i = 0; i < cycles; i++) {
pattern.push(highTime, -lowTime);
}
return "RAW_Data: " + pattern.join(" ");
}
function generatePulseTrain() {
const pulseWidth = parseInt(keyboard.numKeyboard("Pulse width (μs):", "500"));
const pulseGap = parseInt(keyboard.numKeyboard("Pulse gap (μs):", "1500"));
const pulseCount = parseInt(keyboard.numKeyboard("Number of pulses:", "8"));
if (isNaN(pulseWidth) || isNaN(pulseGap) || isNaN(pulseCount)) {
dialog.error("Invalid parameters");
return null;
}
const pattern = [];
for (let i = 0; i < pulseCount; i++) {
pattern.push(pulseWidth, -pulseGap);
}
return "RAW_Data: " + pattern.join(" ");
}
function generateRandomPattern() {
const length = parseInt(keyboard.numKeyboard("Pattern length:", "20"));
const minTime = parseInt(keyboard.numKeyboard("Min time (μs):", "200"));
const maxTime = parseInt(keyboard.numKeyboard("Max time (μs):", "2000"));
if (isNaN(length) || isNaN(minTime) || isNaN(maxTime) || minTime >= maxTime) {
dialog.error("Invalid parameters");
return null;
}
const pattern = [];
for (let i = 0; i < length; i++) {
const time = minTime + Math.random() * (maxTime - minTime);
pattern.push(i % 2 === 0 ? Math.round(time) : -Math.round(time));
}
return "RAW_Data: " + pattern.join(" ");
}
function generateMorsePattern() {
const message = keyboard.keyboard("Morse message:", "HELLO");
const dotTime = parseInt(keyboard.numKeyboard("Dot time (μs):", "500"));
const dashTime = dotTime * 3;
const gapTime = dotTime;
const morseMap = {
'A': '.-', 'B': '-...', 'C': '-.-.', 'D': '-..', 'E': '.', 'F': '..-.',
'G': '--.', 'H': '....', 'I': '..', 'J': '.---', 'K': '-.-', 'L': '.-..',
'M': '--', 'N': '-.', 'O': '---', 'P': '.--.', 'Q': '--.-', 'R': '.-.',
'S': '...', 'T': '-', 'U': '..-', 'V': '...-', 'W': '.--', 'X': '-..-',
'Y': '-.--', 'Z': '--..', ' ': '/'
};
const pattern = [];
for (let char of message.toUpperCase()) {
const morse = morseMap[char];
if (!morse) continue;
if (morse === '/') {
pattern.push(-dashTime * 2); // Word gap
} else {
for (let symbol of morse) {
if (symbol === '.') {
pattern.push(dotTime, -gapTime);
} else if (symbol === '-') {
pattern.push(dashTime, -gapTime);
}
}
pattern.push(-dashTime); // Letter gap
}
}
return "RAW_Data: " + pattern.join(" ");
}
function frequencySweep() {
const startFreq = parseInt(keyboard.numKeyboard("Start freq (Hz):", "433900000"));
const endFreq = parseInt(keyboard.numKeyboard("End freq (Hz):", "433950000"));
const steps = parseInt(keyboard.numKeyboard("Number of steps:", "10"));
if (isNaN(startFreq) || isNaN(endFreq) || isNaN(steps) || startFreq >= endFreq) {
dialog.error("Invalid parameters");
return;
}
const testPattern = "RAW_Data: 1000 -1000 500 -500 1000 -1000";
const stepSize = (endFreq - startFreq) / (steps - 1);
for (let i = 0; i < steps; i++) {
const freq = startFreq + (i * stepSize);
display.fill(0);
display.print(0, 0, "Frequency Sweep", 2);
display.print(0, 25, `Step ${i + 1}/${steps}`, 1);
display.print(0, 40, `${(freq / 1000000).toFixed(3)} MHz`, 1);
console.log(`Sweep step ${i + 1}: ${freq} Hz`);
if (subghz.transmit(Math.round(freq), testPattern)) {
console.log(` Transmitted successfully`);
} else {
console.log(` Transmission failed`);
}
delay(1000);
}
dialog.success("Frequency sweep completed");
}
Signal Analysis and Utilities#
function analyzeSignal(signal) {
const analysis = performSignalAnalysis(signal);
const report = `Signal Analysis Report
=== Basic Information ===
Frequency: ${(signal.frequency / 1000000).toFixed(3)} MHz
Protocol: ${signal.protocol}
RSSI: ${signal.rssi.toFixed(1)} dBm
=== Timing Analysis ===
Total Duration: ${analysis.totalDuration} μs
Number of Transitions: ${analysis.transitionCount}
Average High Time: ${analysis.avgHighTime.toFixed(1)} μs
Average Low Time: ${analysis.avgLowTime.toFixed(1)} μs
Duty Cycle: ${analysis.dutyCycle.toFixed(1)}%
=== Pattern Analysis ===
Shortest Pulse: ${analysis.minPulse} μs
Longest Pulse: ${analysis.maxPulse} μs
Most Common Pulse: ${analysis.commonPulse} μs
Detected Patterns: ${analysis.patterns.length}
=== Quality Assessment ===
Signal Clarity: ${analysis.quality.clarity}
Timing Consistency: ${analysis.quality.consistency}
Recommended Actions: ${analysis.recommendations.join(', ')}`;
dialog.viewText(report, "Signal Analysis");
}
function performSignalAnalysis(signal) {
// Parse raw data
const rawData = signal.rawData.replace('RAW_Data: ', '');
const pulses = rawData.split(' ').map(p => parseInt(p)).filter(p => !isNaN(p));
if (pulses.length === 0) {
return { error: "No valid pulse data found" };
}
const analysis = {
transitionCount: pulses.length,
totalDuration: pulses.reduce((sum, p) => sum + Math.abs(p), 0),
highPulses: pulses.filter(p => p > 0),
lowPulses: pulses.filter(p => p < 0).map(p => Math.abs(p)),
patterns: []
};
// Calculate averages
analysis.avgHighTime = analysis.highPulses.length > 0 ?
analysis.highPulses.reduce((sum, p) => sum + p, 0) / analysis.highPulses.length : 0;
analysis.avgLowTime = analysis.lowPulses.length > 0 ?
analysis.lowPulses.reduce((sum, p) => sum + p, 0) / analysis.lowPulses.length : 0;
analysis.dutyCycle = (analysis.avgHighTime / (analysis.avgHighTime + analysis.avgLowTime)) * 100;
// Find min/max pulses
const allPulsesAbs = pulses.map(p => Math.abs(p));
analysis.minPulse = Math.min(...allPulsesAbs);
analysis.maxPulse = Math.max(...allPulsesAbs);
// Find most common pulse (simplified)
const pulseCounts = {};
allPulsesAbs.forEach(p => {
const rounded = Math.round(p / 100) * 100; // Group by 100μs
pulseCounts[rounded] = (pulseCounts[rounded] || 0) + 1;
});
analysis.commonPulse = parseInt(Object.keys(pulseCounts).reduce((a, b) =>
pulseCounts[a] > pulseCounts[b] ? a : b));
// Quality assessment
analysis.quality = assessSignalQuality(analysis);
analysis.recommendations = generateRecommendations(analysis);
return analysis;
}
function assessSignalQuality(analysis) {
const quality = {};
// Signal clarity based on pulse variation
const pulseVariation = analysis.maxPulse / analysis.minPulse;
if (pulseVariation < 2) {
quality.clarity = "Excellent";
} else if (pulseVariation < 5) {
quality.clarity = "Good";
} else if (pulseVariation < 10) {
quality.clarity = "Fair";
} else {
quality.clarity = "Poor";
}
// Timing consistency based on duty cycle
if (analysis.dutyCycle > 30 && analysis.dutyCycle < 70) {
quality.consistency = "Good";
} else if (analysis.dutyCycle > 20 && analysis.dutyCycle < 80) {
quality.consistency = "Fair";
} else {
quality.consistency = "Poor";
}
return quality;
}
function generateRecommendations(analysis) {
const recommendations = [];
if (analysis.quality.clarity === "Poor") {
recommendations.push("Check signal source and reduce interference");
}
if (analysis.quality.consistency === "Poor") {
recommendations.push("Verify timing accuracy and transmission stability");
}
if (analysis.transitionCount < 10) {
recommendations.push("Signal may be too short for reliable transmission");
}
if (analysis.totalDuration > 100000) {
recommendations.push("Consider splitting long signals into segments");
}
if (recommendations.length === 0) {
recommendations.push("Signal appears suitable for transmission");
}
return recommendations;
}
function signalUtilities() {
while (true) {
const choice = dialog.choice([
"Signal Converter",
"Frequency Calculator",
"Timing Optimizer",
"Protocol Detector",
"Signal Generator",
"Exit"
]);
switch (choice) {
case 0:
signalConverter();
break;
case 1:
frequencyCalculator();
break;
case 2:
timingOptimizer();
break;
case 3:
protocolDetector();
break;
case 4:
signalGenerator();
break;
case 5:
return;
}
}
}
function frequencyCalculator() {
const input = keyboard.keyboard("Frequency (MHz or Hz):", "433.92");
if (!input) return;
let freq;
if (input.includes('.')) {
// MHz input
freq = parseFloat(input) * 1000000;
} else {
// Hz input
freq = parseInt(input);
}
if (isNaN(freq)) {
dialog.error("Invalid frequency");
return;
}
const wavelength = 299792458 / freq; // c / f
const period = 1 / freq;
const info = `Frequency Analysis:
Input: ${input}
Frequency: ${freq.toLocaleString()} Hz
Frequency: ${(freq / 1000000).toFixed(3)} MHz
Wavelength: ${wavelength.toFixed(3)} m
Period: ${(period * 1000000).toFixed(3)} μs
Common Bands:
${freq >= 315000000 && freq <= 315250000 ? '✓' : '○'} 315 MHz (US/Canada)
${freq >= 433050000 && freq <= 434790000 ? '✓' : '○'} 433 MHz (ISM Global)
${freq >= 868000000 && freq <= 868600000 ? '✓' : '○'} 868 MHz (Europe)
${freq >= 902000000 && freq <= 928000000 ? '✓' : '○'} 915 MHz (US/Canada)`;
dialog.viewText(info, "Frequency Analysis");
}
Technical Specifications#
Frequency Ranges#
- 315 MHz: 315.000 - 315.250 MHz (US/Canada)
- 433 MHz: 433.050 - 434.790 MHz (ISM Band, Global)
- 868 MHz: 868.000 - 868.600 MHz (Europe)
- 915 MHz: 902.000 - 928.000 MHz (US/Canada)
Supported Protocols#
- OOK (On-Off Keying)
- ASK (Amplitude Shift Keying)
- FSK (Frequency Shift Keying)
- MSK (Minimum Shift Keying)
Signal Format#
Sub-GHz files typically use Flipper Zero format: