Files
giglez/3_rf_spectrum_analyzer.js
Trilltechnician 48fcb00241 Phase 3 Complete: Web Interface MVP
Major Achievements:
-  Full web interface (1,520+ lines of frontend code)
-  Interactive Leaflet.js map with marker clustering
-  Drag-and-drop upload system with GPS input
-  Search & filter UI with multi-criteria
-  Statistics dashboard with Chart.js
-  Responsive mobile-friendly design

Backend:
-  FastAPI static file serving
-  Simplified server mode (main_simple.py)
-  Improved startup script with port auto-selection
-  PostgreSQL schema ready (requires setup)

Database:
-  SQLite populated with 85 Flipper Zero signatures
-  Device matching system operational
-  Frequency-based search working

Documentation:
-  PHASE_3_COMPLETE.md - Technical summary
-  WEB_INTERFACE_README.md - User guide
-  WEBAPP_STARTUP_GUIDE.md - Troubleshooting
-  POSTGRESQL_SETUP_EXPLANATION.md - DB setup guide
-  DATABASE_POPULATION_SUCCESS.md - Import report
-  DEVICE_IDENTIFICATION_REPORT.md - Matching analysis

Files Created:
- templates/index.html (260 lines)
- static/css/main.css (500 lines)
- static/js/*.js (760 lines total)
- src/api/main_simple.py (simplified server)
- start_web.sh (auto port selection)

Status: Production MVP Ready
Next: Phase 4 - API & Integration

🛰️ Generated with Claude Code
https://claude.com/claude-code

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-12 18:21:11 -08:00

269 lines
7.0 KiB
JavaScript

/**
* 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);
}