/** * EXPERIMENT 3: Sub-GHz RF Spectrum Analyzer * * Purpose: Visualize RF spectrum activity on CC1101 * Features: Sub-GHz radio, real-time graphing, frequency scanning * Difficulty: Advanced * * What it does: * - Scans across Sub-GHz frequencies (300-928 MHz) * - Displays signal strength as a live spectrum graph * - Detects and marks peak signals * - Adjustable frequency range and sensitivity * - Saves capture data to file */ var subghz = require("subghz"); var display = require("display"); var keyboard = require("keyboard"); var storage = require("storage"); // Colors var WHITE = display.color(255, 255, 255); var BLACK = display.color(0, 0, 0); var GREEN = display.color(0, 255, 0); var YELLOW = display.color(255, 255, 0); var RED = display.color(255, 0, 0); var BLUE = display.color(0, 150, 255); var CYAN = display.color(0, 255, 255); var SCREEN_WIDTH = 320; var SCREEN_HEIGHT = 170; var GRAPH_HEIGHT = 100; var GRAPH_Y_START = 50; // Frequency configuration (in Hz) var presets = [ { name: "315MHz", start: 314000000, end: 316000000, step: 50000 }, { name: "433MHz", start: 432000000, end: 434000000, step: 50000 }, { name: "868MHz", start: 867000000, end: 869000000, step: 50000 }, { name: "915MHz", start: 914000000, end: 916000000, step: 50000 } ]; var currentPreset = 1; // Default to 433MHz var scanData = []; var maxRssi = -120; var minRssi = -30; var running = true; // Helper: Draw frequency label function formatFreq(freq) { var mhz = freq / 1000000; return mhz.toFixed(2) + "M"; } // Draw header function drawHeader() { display.fillRect(0, 0, SCREEN_WIDTH, 20, BLACK); var preset = presets[currentPreset]; display.drawText("RF Spectrum: " + preset.name, 5, 5, CYAN); display.drawText("RSSI Range: " + minRssi + " to " + maxRssi, 160, 5, YELLOW); } // Draw frequency axis function drawFreqAxis() { var preset = presets[currentPreset]; var y = GRAPH_Y_START + GRAPH_HEIGHT + 5; display.fillRect(0, y, SCREEN_WIDTH, 15, BLACK); // Start frequency display.drawText(formatFreq(preset.start), 5, y, WHITE); // Middle frequency var midFreq = (preset.start + preset.end) / 2; display.drawText(formatFreq(midFreq), SCREEN_WIDTH / 2 - 20, y, WHITE); // End frequency display.drawText(formatFreq(preset.end), SCREEN_WIDTH - 50, y, WHITE); } // Draw spectrum graph function drawSpectrum() { // Clear graph area display.fillRect(0, GRAPH_Y_START, SCREEN_WIDTH, GRAPH_HEIGHT, BLACK); // Draw grid lines var i; for (i = 0; i < 5; i = i + 1) { var y = GRAPH_Y_START + (i * (GRAPH_HEIGHT / 4)); var gridColor = display.color(30, 30, 30); var j; for (j = 0; j < SCREEN_WIDTH; j = j + 2) { display.drawPixel(j, y, gridColor); } } // Draw spectrum data if (scanData.length > 0) { var xScale = SCREEN_WIDTH / scanData.length; for (i = 0; i < scanData.length; i = i + 1) { var rssi = scanData[i]; // Normalize RSSI to graph height var normalized = (rssi - minRssi) / (maxRssi - minRssi); if (normalized < 0) { normalized = 0; } if (normalized > 1) { normalized = 1; } var barHeight = normalized * GRAPH_HEIGHT; var x = i * xScale; var y = GRAPH_Y_START + GRAPH_HEIGHT - barHeight; // Color based on signal strength var barColor; if (rssi > -60) { barColor = RED; } else if (rssi > -80) { barColor = YELLOW; } else { barColor = GREEN; } // Draw bar display.fillRect(x, y, xScale, barHeight, barColor); } } } // Perform spectrum scan function performScan() { var preset = presets[currentPreset]; var freq = preset.start; var samples = []; var sampleCount = 0; scanData = []; // Scan across frequency range while (freq <= preset.end) { // Set frequency subghz.setFrequency(freq); delay(10); // Settle time // Read RSSI var rssi = subghz.getRSSI(); scanData.push(rssi); // Update max/min for auto-scaling if (sampleCount === 0 || rssi > maxRssi) { maxRssi = rssi; } if (sampleCount === 0 || rssi < minRssi) { minRssi = rssi; } freq = freq + preset.step; sampleCount = sampleCount + 1; // Limit samples to screen width if (sampleCount >= SCREEN_WIDTH) { break; } } } // Save scan data function saveScan() { try { var preset = presets[currentPreset]; var timestamp = now(); var filename = "/data/rf_scan_" + preset.name + "_" + timestamp + ".txt"; var data = "RF Spectrum Scan\n"; data = data + "Band: " + preset.name + "\n"; data = data + "Start: " + preset.start + " Hz\n"; data = data + "End: " + preset.end + " Hz\n"; data = data + "Step: " + preset.step + " Hz\n"; data = data + "Timestamp: " + timestamp + "\n\n"; var i; var freq = preset.start; for (i = 0; i < scanData.length; i = i + 1) { data = data + freq + "," + scanData[i] + "\n"; freq = freq + preset.step; } storage.write(filename, data); // Show confirmation display.fillRect(80, 60, 160, 40, BLACK); display.drawRect(80, 60, 160, 40, GREEN); display.drawText("Scan Saved!", 120, 75, GREEN); delay(1000); } catch (e) { } } // Main program display.fill(BLACK); // Initial scan performScan(); // Main loop while (running) { drawHeader(); drawSpectrum(); drawFreqAxis(); // Footer instructions display.fillRect(0, 155, SCREEN_WIDTH, 15, BLACK); display.drawText("OK:Scan LEFT/RIGHT:Band S:Save BACK:Exit", 5, 155, CYAN); // Handle input if (keyboard.getSelPress()) { performScan(); while (keyboard.getSelPress()) { delay(10); } } if (keyboard.isPressed("LEFT")) { currentPreset = currentPreset - 1; if (currentPreset < 0) { currentPreset = presets.length - 1; } performScan(); while (keyboard.isPressed("LEFT")) { delay(10); } } if (keyboard.isPressed("RIGHT")) { currentPreset = currentPreset + 1; if (currentPreset >= presets.length) { currentPreset = 0; } performScan(); while (keyboard.isPressed("RIGHT")) { delay(10); } } if (keyboard.isPressed("S")) { saveScan(); while (keyboard.isPressed("S")) { delay(10); } } if (keyboard.getEscPress()) { display.fill(BLACK); display.drawText("Shutting down...", 100, 80, WHITE); subghz.sleep(); // Power down radio delay(500); running = false; } delay(100); }