diff --git a/README.md b/README.md index 214a726..7ec7225 100644 --- a/README.md +++ b/README.md @@ -7,29 +7,22 @@ ESP32-S3 + dual CC1101 + external amplifiers + OLED display + web interface. ## WHAT IT DOES -Every car key fob on the market operates on one of two narrow sub-GHz bands. -This device sweeps both bands simultaneously with continuous FM noise, leaving -zero gaps between hops and zero time for a fob transmission to get through. +Most car key fobs that matter for NA vs EU/global boil down to two on-air +channels: about **315 MHz** (North America) and **433.92 MHz** (Europe and much +of the rest of the world). - 300 - 320 MHz North American band - Honda/Acura 303.825 MHz - Chamberlain/LiftMaster 310.0 MHz - Toyota/Lexus/Scion 314.98 MHz - Ford/GM/Chrysler/Dodge/Jeep 315.0 MHz - Linear Delta-3 / LiftMaster 318.0 MHz +This firmware **does not sweep** those bands anymore. Each CC1101 **locks** on +one frequency and stays there at full configured TX power: - 390 - 436 MHz European and global band - Chamberlain/LiftMaster 390.0 MHz - Holtek-based remotes 418.0 MHz - Somfy RTS / SMC 5326 433.42 MHz - BMW/VW/Audi/Mercedes/Hyundai/Kia 433.92 MHz - Asian/Euro fobs 434.42 MHz + Radio 1 315.000 MHz Narrow FM deviation + Galois LFSR on GDO0 + (energy concentrated on the NA fob channel) -A fob button press generates a 200-500ms transmission window. -Radio 1 completes a full sweep of 300-320 MHz every 75ms. -Radio 2 completes a full sweep of 390-436 MHz every 180ms. -Every target frequency gets hit multiple times per fob press. -The car never receives a clean signal. + Radio 2 433.920 MHz Maximum CC1101 FM deviation + same LFSR + (wide, loud noise on the dominant EU/global ISM fob channel) + +Both antennas scream continuously while jamming is enabled — no hopping, no +dwell time, no split energy across a span. The web UI and OLED show these as +locked carriers (`jam_fixed` in telemetry). --- @@ -57,17 +50,17 @@ The car never receives a clean signal. GPIO 12 SCK GPIO 13 MISO - [CC1101 NUMBER 1 - 300-320 MHz sweep] + [CC1101 NUMBER 1 - 315 MHz locked jam] GPIO 7 CS (chip select, dedicated) - GPIO 4 GDO0 (LEDC PWM noise output) + GPIO 4 GDO0 (LFSR noise into direct async TX) 3V3 VCC GND GND - [CC1101 NUMBER 2 - 390-436 MHz sweep] + [CC1101 NUMBER 2 - 433.92 MHz locked jam] GPIO 8 CS (chip select, dedicated) - GPIO 5 GDO0 (LEDC PWM noise output) + GPIO 5 GDO0 (LFSR noise into direct async TX) 3V3 VCC GND GND @@ -97,33 +90,24 @@ The car never receives a clean signal. [RF CONFIGURATION] - Modulation FM noise (direct async TX via GDO0 LEDC PWM) - Frequency deviation 380 kHz (CC1101 hardware maximum) - Noise bandwidth ~1010 kHz per hop (Carson rule: 2 x (380 + 125)) - Bitrate 250 kbps + Carrier plan Radio 1 locked 315.000 MHz, Radio 2 locked 433.920 MHz + Modulation FM from Galois LFSR bitstream on GDO0 (direct async TX) + Deviation R1 25 kHz (narrow — energy on 315) + Deviation R2 380 kHz (CC1101 max — wide noise on 433.92) + LFSR clock 50 kHz (hardware timer ISR) + Bitrate (RadioLib) 250 kbps context for begin() RX bandwidth 812 kHz (maximum) TX power -30 / -20 / -15 / -10 / 0 / 5 / 7 / 10 dBm (8 steps) Default TX power 10 dBm Amplifier gain +20 dB (configurable in web UI) Effective output 30 dBm / ~1 Watt (with amplifiers, at 10 dBm TX) - [SWEEP CONFIGURATION - defaults] + [LEGACY SWEEP SETTINGS IN WEB UI / NVS] - Radio 1 center 310.0 MHz - Radio 1 span 20.0 MHz (300 - 320 MHz) - Radio 1 steps 25 (0.83 MHz spacing, within 1 MHz hop width) - Radio 1 cycle time 75 ms (25 steps x 3ms dwell) - - Radio 2 center 413.0 MHz - Radio 2 span 46.0 MHz (390 - 436 MHz) - Radio 2 steps 60 (0.77 MHz spacing, within 1 MHz hop width) - Radio 2 cycle time 180 ms (60 steps x 3ms dwell) - - Dwell per hop 3 ms - Gap between hops none (hop bandwidth > step spacing) - -All sweep parameters are adjustable live from the web interface and persist -across reboots via NVS (ESP32 non-volatile storage). + Dwell, steps, and span are still saved to NVS if you use Apply Sweep. + Fixed-carrier jamming does not use them. Tunables are in config.h: + JAM_LOCK_FREQ_1_MHZ, JAM_LOCK_FREQ_2_MHZ, JAM_DEV_KHZ_R1_NARROW, + JAM_DEV_KHZ_R2_WIDE. --- @@ -136,11 +120,11 @@ Three dot indicators in the header show the current page. [PAGE 0 - STATUS] (default) - Header: inverted bar reading JAMMING ACTIVE when running, STANDBY when not + Header: inverted bar reading LOCKED JAM when running, STANDBY when not Row 1: ANT1 current frequency in MHz with animated radio-wave arcs Row 2: ANT2 current frequency in MHz with animated radio-wave arcs Row 3: TX power breakdown: radio dBm + amp gain = effective dBm - Row 4: FULL DUAL-BAND TX badge when both radios active, else temp and heap + Row 4: 315 + 433.92 LOCK badge when both radios active, else temp and heap Row 5: uptime since boot [PAGE 1 - FREQ AND HOPS] @@ -219,18 +203,13 @@ Connect to the WiFi access point, then open the control panel in a browser. Start / Stop jamming toggle TX power selector (8 levels: -30 dBm to 10 dBm) Amplifier gain input (dB, affects displayed effective power only) - Sweep dwell time (ms per hop) - Radio 1 steps and span (MHz) - Radio 2 steps and span (MHz) + Sweep tuning fields (legacy — stored in NVS only, not used for jam) All settings persist to NVS on save [VISUALIZATIONS] - Two canvas sweep displays, one per radio band. - Each shows the sweep range with named frequency markers: - 315 MHz band: Honda, Toyota, LiftMaster, Ford/GM markers - 433 MHz band: LiftMaster 390, Holtek 418, Somfy, BMW/VW/Audi markers - Current hop position shown with a heat trail fading over recent positions. + Two canvas displays show a narrow window around each locked carrier (markers + for common fob channels still drawn where they fall in range). Canvas height 90px, updates every 1 second with telemetry. Two sparkline charts showing 2-minute rolling history: @@ -247,9 +226,10 @@ Connect to the WiFi access point, then open the control panel in a browser. Minimum heap KB recorded Hop count Radio 1 (total since boot) Hop count Radio 2 (total since boot) - Combined hops per second + Hop counters (stay at 0 in fixed-carrier mode; kept for API compatibility) WiFi clients on AP Nodes (ESP-NOW): count of other boards running this firmware in range + Jam mode: LOCKED when jam_fixed is true in telemetry Uptime 24-hour mission progress bar in the header @@ -349,8 +329,7 @@ The system is designed to run unattended at full power indefinitely. [KEY FUNCTIONS IN MAIN.CPP] - startJamming() initializes both CC1101s, sets power, starts async TX - tickSweep() advances one radio by one hop step + startJamming() initializes both CC1101s, locks freqs, sets deviation, async TX oledTick() OLED update loop, handles pages and notifications oledDrawStatus() page 0 renderer oledDrawFreq() page 1 renderer @@ -359,19 +338,16 @@ The system is designed to run unattended at full power indefinitely. oledBootMsg() synchronous boot status message encISR() rotary encoder interrupt service routine handleToggle() HTTP handler: start/stop jamming - handleSettings() HTTP handler: update sweep and power parameters + handleSettings() HTTP handler: update TX power handleTelemetry() HTTP handler: JSON status for web UI polling handleLogs() HTTP handler: timestamped log ring buffer handleRoot() HTTP handler: serves embedded web UI HTML logLine() timestamped log entry to ring buffer and Serial - loop() sweeps both radios, runs OLED, runs OTA, watchdog + loop() ESP-NOW tick, HTTP, OLED, capture FSM, watchdog - [SWEEP LOOP TIMING] + [JAM TIMING] - Both radios are swept independently in the same loop() pass. - tickSweep() is a no-op if less than sweepDwellMs have elapsed. - There are no blocking delays in the main loop. - OLED and web server run interleaved with no impact on sweep timing. + Carriers are fixed after startJamming(); no hop loop. LFSR ISR runs at 50 kHz. --- diff --git a/include/config.h b/include/config.h index 659e48f..c7d22e8 100644 --- a/include/config.h +++ b/include/config.h @@ -33,14 +33,19 @@ // External amplifier gain in dB (used only for display — does not affect CC1101 output) #define DEFAULT_AMP_GAIN_DB 20 -// Modulation parameters for jamming -// Deviation 380 kHz = CC1101 hardware maximum. -// Carson's rule BW ≈ 2*(380 + 125) ≈ 1010 kHz of noise per hop. -// With ~1 MHz per hop we get solid overlap between steps and leave no gaps. -#define JAM_BITRATE_KBPS 250.0f // 250 kbps → 125 kHz baseband, maximises noise energy -#define JAM_FREQ_DEV_KHZ 380.0f // CC1101 max deviation → ~1 MHz noise per hop (was 120) -#define JAM_RX_BW_KHZ 812.0f // Maximum RX BW +// Modulation parameters for jamming (LFSR drives GDO0 in direct async TX) +#define JAM_BITRATE_KBPS 250.0f // baseband / channel filter context for RadioLib begin() +#define JAM_FREQ_DEV_KHZ 380.0f // default passed to begin(); per-radio deviation applied after init +#define JAM_RX_BW_KHZ 812.0f // wide RX BW for begin() +// Fixed dual-carrier jamming — NO sweep: each radio holds one frequency at full TX power. +// R2 (433.92) uses max deviation = loudest/widest noise; R1 (315) uses narrow deviation. +#define JAM_LOCK_FREQ_1_MHZ 315.0f +#define JAM_LOCK_FREQ_2_MHZ 433.92f +#define JAM_DEV_KHZ_R2_WIDE 380.0f // CC1101 max — "baby screaming" on EU/global fob channel +#define JAM_DEV_KHZ_R1_NARROW 25.0f // minimal FM swing — energy concentrated on NA 315 MHz + +// Legacy sweep constants (NVS + API still accept them; firmware no longer hops) // Frequency sweep — full coverage of all known car-key-fob sub-GHz bands // // Radio 1 (CC1101 #1) — 300–320 MHz [CC1101 Band 1: 300–348 MHz] diff --git a/src/main.cpp b/src/main.cpp index a761b6a..576f0ba 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,10 +1,9 @@ /** * Dual CC1101 always-on key-fob jammer. * ESP32-S3 DevKitC-1: two CC1101 on shared SPI. - * Radio 1: sweeps 300–320 MHz (US band — Honda 303.825, Toyota 315, Ford/GM/Chrysler 315, Linear 318 MHz) - * Radio 2: sweeps 390–436 MHz (EU/global — LiftMaster 390, Holtek 418, Somfy 433.42, EU 433.92, Nero 434.42 MHz) - * FM noise via Galois LFSR ISR on GDO0 pins — spectrally flat broadband noise, no discrete sidebands. - * WiFi AP + web UI on boot; OTA updates via ArduinoOTA. + * Radio 1: fixed 315.0 MHz (narrow FM deviation) + LFSR on GDO0. + * Radio 2: fixed 433.92 MHz (max FM deviation) + LFSR — dominant EU/global fob channel. + * WiFi AP + web UI on boot; OTA updates; ESP-NOW peer discovery. */ #include @@ -54,28 +53,11 @@ static uint32_t uptimeStart = 0; static float currentRssi1 = NAN; static float currentRssi2 = NAN; -// Frequency sweep state -static uint8_t sweepStep1 = 0; -static uint8_t sweepStep2 = 0; -static uint32_t lastSweep1Ms = 0; -static uint32_t lastSweep2Ms = 0; -static float sweepFreq1 = SWEEP_1_CENTER_MHZ; -static float sweepFreq2 = SWEEP_2_CENTER_MHZ; +// Locked jam frequencies (for telemetry / web graphs); no hopping +static float sweepFreq1 = JAM_LOCK_FREQ_1_MHZ; +static float sweepFreq2 = JAM_LOCK_FREQ_2_MHZ; -// Fast Frequency Hopping / VCO Calibration Caching -// By caching the CC1101 PLL calibration registers for each sweep frequency, -// we bypass the 720µs auto-calibration during the sweep, reducing hop dead-time -// from ~750µs down to ~40µs (SPI transaction time). This increases jamming efficiency -// from ~76% to >98% at a 3ms dwell time. -struct SweepStepCache { - float freqMhz; - uint8_t freqRegs[3]; // FREQ2, FREQ1, FREQ0 - uint8_t fscalRegs[3]; // FSCAL3, FSCAL2, FSCAL1 -}; -static SweepStepCache sweepTable1[100]; -static SweepStepCache sweepTable2[100]; - -// Runtime-adjustable sweep parameters (loaded from NVS) +// Legacy sweep parameters (still in NVS / API; fixed-carrier jam does not use them) static uint32_t sweepDwellMs = SWEEP_DWELL_MS; static uint8_t sweep1Steps = SWEEP_1_STEPS; static uint8_t sweep2Steps = SWEEP_2_STEPS; @@ -493,15 +475,7 @@ static String capAnalyze() { return String(buf); } -// ─── Raw SPI Helpers for Fast Sweep ──────────────────────────────────────────── -static void spiStrobe(uint8_t csPin, uint8_t strobe) { - spi.beginTransaction(SPISettings(SPI_SPEED_HZ, MSBFIRST, SPI_MODE0)); - digitalWrite(csPin, LOW); - spi.transfer(strobe); - digitalWrite(csPin, HIGH); - spi.endTransaction(); -} - +// ─── Raw SPI (PATABLE burst for OOK replay) ────────────────────────────────── static void spiWriteReg(uint8_t csPin, uint8_t reg, uint8_t val) { spi.beginTransaction(SPISettings(SPI_SPEED_HZ, MSBFIRST, SPI_MODE0)); digitalWrite(csPin, LOW); @@ -511,58 +485,6 @@ static void spiWriteReg(uint8_t csPin, uint8_t reg, uint8_t val) { spi.endTransaction(); } -static uint8_t spiReadReg(uint8_t csPin, uint8_t reg) { - spi.beginTransaction(SPISettings(SPI_SPEED_HZ, MSBFIRST, SPI_MODE0)); - digitalWrite(csPin, LOW); - spi.transfer(reg | 0x80); // Read bit - uint8_t val = spi.transfer(0x00); - digitalWrite(csPin, HIGH); - spi.endTransaction(); - return val; -} - -static uint8_t spiReadStatusReg(uint8_t csPin, uint8_t reg) { - spi.beginTransaction(SPISettings(SPI_SPEED_HZ, MSBFIRST, SPI_MODE0)); - digitalWrite(csPin, LOW); - spi.transfer(reg | 0xC0); // Read bit + Burst bit for status registers - uint8_t val = spi.transfer(0x00); - digitalWrite(csPin, HIGH); - spi.endTransaction(); - return val; -} - -// Pre-compute and cache the PLL calibration for all frequencies in a sweep. -static void buildSweepTable(CC1101& radio, uint8_t csPin, SweepStepCache* table, uint8_t steps, float center, float span) { - logLine("[SWEEP] Building VCO calibration table for CS " + String(csPin)); - const float divisor = (steps > 1) ? (float)(steps - 1) : 1.0f; - - for (uint8_t i = 0; i < steps; i++) { - float freq = center - (span / 2.0f) + (span / divisor) * (float)i; - table[i].freqMhz = freq; - - radio.standby(); - radio.setFrequency(freq); - - spiStrobe(csPin, 0x33); // SCAL strobe forces calibration - - uint32_t start = millis(); - while ((spiReadStatusReg(csPin, 0x38) & 0x1F) != 0x01) { // MARCSTATE == 0x01 (IDLE) - if (millis() - start > 50) { - logLine("[SWEEP] VCO cal timeout at " + String(freq) + " MHz"); - break; - } - } - - table[i].freqRegs[0] = spiReadReg(csPin, 0x0D); // FREQ2 - table[i].freqRegs[1] = spiReadReg(csPin, 0x0E); // FREQ1 - table[i].freqRegs[2] = spiReadReg(csPin, 0x0F); // FREQ0 - - table[i].fscalRegs[0] = spiReadReg(csPin, 0x23); // FSCAL3 - table[i].fscalRegs[1] = spiReadReg(csPin, 0x24); // FSCAL2 - table[i].fscalRegs[2] = spiReadReg(csPin, 0x25); // FSCAL1 - } -} - // Manually probe a CC1101 via raw SPI to verify bus connectivity. // Reads the VERSION register (0xF1 = burst read of reg 0x31). // Returns the raw byte, or 0xFF if bus appears dead. @@ -620,7 +542,8 @@ static void startJamming() { logLine("[R1] init failed: " + String(st1)); } else { radio1Status = 1; - buildSweepTable(radio1, CC1101_1_CS, sweepTable1, sweep1Steps, SWEEP_1_CENTER_MHZ, sweep1SpanMhz); + radio1.setFrequency(JAM_LOCK_FREQ_1_MHZ); + radio1.setFrequencyDeviation(JAM_DEV_KHZ_R1_NARROW); } // Initialize radio 2 with retries @@ -635,15 +558,18 @@ static void startJamming() { logLine("[R2] init failed: " + String(st2)); } else { radio2Status = 1; - buildSweepTable(radio2, CC1101_2_CS, sweepTable2, sweep2Steps, SWEEP_2_CENTER_MHZ, sweep2SpanMhz); + radio2.setFrequency(JAM_LOCK_FREQ_2_MHZ); + radio2.setFrequencyDeviation(JAM_DEV_KHZ_R2_WIDE); } + + sweepFreq1 = JAM_LOCK_FREQ_1_MHZ; + sweepFreq2 = JAM_LOCK_FREQ_2_MHZ; // Start both radios transmitting simultaneously int stTx1 = RADIOLIB_ERR_NONE; int stTx2 = RADIOLIB_ERR_NONE; - // Start LFSR noise generator — drives GDO0 pins from a 50 kHz hardware timer ISR, - // producing spectrally flat pseudo-random broadband FM noise (~810 kHz per hop). + // LFSR noise generator — 50 kHz ISR on GDO0; R1 narrow-dev FM on 315 MHz, R2 max-dev on 433.92 MHz. noiseGenStart(); if (radio1Status == 1) { @@ -669,9 +595,9 @@ static void startJamming() { } if (radio1Status == 2 || radio2Status == 2) { - logLine("[JAM] Jamming active (async FM noise mode):"); - logLine("[JAM] Radio 1: sweep 300-320 MHz at " + String(jamPower) + " dBm (status: " + String(radio1Status == 2 ? "TX" : "FAIL") + ")"); - logLine("[JAM] Radio 2: sweep 390-436 MHz at " + String(jamPower) + " dBm (status: " + String(radio2Status == 2 ? "TX" : "FAIL") + ")"); + logLine("[JAM] Fixed-carrier jamming (no sweep):"); + logLine("[JAM] R1: " + String(JAM_LOCK_FREQ_1_MHZ, 2) + " MHz narrow FM @ " + String(jamPower) + " dBm (" + String(radio1Status == 2 ? "TX" : "off") + ")"); + logLine("[JAM] R2: " + String(JAM_LOCK_FREQ_2_MHZ, 2) + " MHz max FM noise @ " + String(jamPower) + " dBm (" + String(radio2Status == 2 ? "TX" : "off") + ")"); } else { logLine("[JAM] Both radios failed to start - check SPI connections"); logLine("[JAM] R1 error: " + radio1Error); @@ -791,7 +717,7 @@ static void oledDrawStatus() { } else if (jam) { u8g2.drawBox(0, 0, 110, 13); u8g2.setDrawColor(0); - u8g2.drawStr(2, 10, ">> JAMMING ACTIVE <<"); + u8g2.drawStr(2, 10, ">> LOCKED JAM <<"); u8g2.setDrawColor(1); } else { u8g2.drawStr(2, 10, "-- STANDBY --"); @@ -868,7 +794,7 @@ static void oledDrawStatus() { // Row 4: temp + heap OR FULL TX badge if (jam && r1 && r2) { - u8g2.drawStr(0, 55, "[ FULL DUAL-BAND TX ]"); + u8g2.drawStr(0, 55, "[ 315 + 433.92 LOCK ]"); } else { char buf[28]; snprintf(buf, sizeof(buf), "%.1fC %lukB", @@ -1014,10 +940,9 @@ static void oledTick() { // // A 32-bit Galois LFSR clocked at 50 kHz generates a maximal-length pseudo- // random bit sequence (period 2^32-1 = ~23.8 hours at 50 kbps). The output -// is spectrally flat: power spreads uniformly across the noise bandwidth -// instead of concentrating at harmonics. Combined with 380 kHz CC1101 -// deviation this gives ~810 kHz of flat FM noise per hop — indistinguishable -// from thermal noise to any receiver. +// is spectrally flat: power spreads uniformly across the modulated bandwidth. +// R2 (433.92 MHz) uses max CC1101 deviation (~810 kHz FM noise). R1 (315 MHz) +// uses narrow deviation so most energy stays on-channel. // // Polynomial 0xB4BCD35C: taps at bits 0,2,6,7,16,18,19,21 — proven maximal. // Both radios use different bit positions of the same sequence for uncorrelated @@ -1051,7 +976,6 @@ static void noiseGenStart() { // Hardware timer at 50 kHz — true ISR, no jitter, no FreeRTOS overhead. // prescaler 80 → 1 MHz tick, alarm at 20 = 20 µs period = 50 kHz. - // Noise BW: 2*(380 kHz dev + 25 kHz baseband) = 810 kHz — solid coverage. s_noiseTimer = timerBegin(2, 80, true); // timer 2, 1 MHz, count up timerAttachInterrupt(s_noiseTimer, &noiseISR, true); // edge triggered timerAlarmWrite(s_noiseTimer, 20, true); // 20 µs auto-reload @@ -1069,16 +993,20 @@ static void updateJamPower(uint8_t idx) { jamPower = newDbm; preferences.putInt("jamPowerIdx", jamPowerIdx); - if (radio1Status >= 1) { - int st1 = radio1.setOutputPower(newDbm); - if (st1 != RADIOLIB_ERR_NONE) { - radio1Error = "Power update failed: " + String(st1); - logLine("[R1] setOutputPower(" + String(newDbm) + ") failed: " + String(st1)); - } else { - radio1Error = ""; - logLine("[R1] TX power -> " + String(newDbm) + " dBm"); + if (radio1Status >= 1) { + int st1 = radio1.setOutputPower(newDbm); + if (st1 != RADIOLIB_ERR_NONE) { + radio1Error = "Power update failed: " + String(st1); + logLine("[R1] setOutputPower(" + String(newDbm) + ") failed: " + String(st1)); + } else { + radio1Error = ""; + logLine("[R1] TX power -> " + String(newDbm) + " dBm"); + if (radio1Status == 2) { + radio1.setFrequency(JAM_LOCK_FREQ_1_MHZ); + radio1.setFrequencyDeviation(JAM_DEV_KHZ_R1_NARROW); + } + } } - } if (radio2Status >= 1) { int st2 = radio2.setOutputPower(newDbm); @@ -1088,6 +1016,10 @@ static void updateJamPower(uint8_t idx) { } else { radio2Error = ""; logLine("[R2] TX power -> " + String(newDbm) + " dBm"); + if (radio2Status == 2) { + radio2.setFrequency(JAM_LOCK_FREQ_2_MHZ); + radio2.setFrequencyDeviation(JAM_DEV_KHZ_R2_WIDE); + } } } @@ -1173,7 +1105,7 @@ h1{animation:flicker .4s ease-out} -
ESP32-S3 • 300–320 MHz + 390–436 MHz • Dual-band FM noise sweep
+
ESP32-S3 • LOCK 315 MHz + LOCK 433.92 MHz • Dual-carrier LFSR jam
@@ -1201,7 +1133,7 @@ h1{animation:flicker .4s ease-out}
Temp
°C
Free Heap
kB
Min Heap
kB
-
Dwell
ms
+
Jam mode
Hops R1
Hops R2
Hops/sec
@@ -1211,17 +1143,17 @@ h1{animation:flicker .4s ease-out}
-

Live Frequency Sweep

+

Locked jam carriers

-  Radio 1 — 300–320 MHz (Honda 303.825 · Toyota 314.98 · Ford/GM 315 · Linear 318) +  Radio 1 — 315.000 MHz (NA) narrow FM + LFSR
-  Radio 2 — 390–436 MHz (LiftMaster 390 · Holtek 418 · Somfy 433.42 · EU 433.92 · Nero 434.42) +  Radio 2 — 433.920 MHz (EU/global) max FM noise + LFSR
@@ -1246,11 +1178,11 @@ h1{animation:flicker .4s ease-out}

Radio Status

-
Radio 1 — 300–320 MHz
+
Radio 1 — 315 MHz LOCK
-
Radio 2 — 390–436 MHz
+
Radio 2 — 433.92 MHz LOCK
@@ -1275,9 +1207,9 @@ h1{animation:flicker .4s ease-out}

-

Sweep Tuning

+

Sweep Tuning (legacy — fixed jam ignores)

-
+
@@ -1458,7 +1390,7 @@ function applyTelemetry(t){ document.getElementById('banner').className='banner '+(jam?'ban-on':'ban-off'); document.getElementById('bt').textContent=jam?'◉ JAMMING ACTIVE ◉':'STANDBY'; document.getElementById('bt').style.color=jam?'#86f28a':'#f28a86'; - const bands=[];if(t.radio1_active)bands.push('300–320 MHz');if(t.radio2_active)bands.push('390–436 MHz'); + const bands=[];if(t.radio1_active)bands.push('315 MHz LOCK');if(t.radio2_active)bands.push('433.92 MHz LOCK'); document.getElementById('bs').textContent=jam&&bands.length ?`${bands.join(' + ')} | ${t.jam_power}dBm + ${t.amp_gain_db}dB amp = ${t.eff_power_dbm}dBm (${(t.eff_power_w*1000).toFixed(0)}mW)` :(jam?'No radios active':'Ready — press Start Jamming'); @@ -1472,7 +1404,8 @@ function applyTelemetry(t){ const tEl=document.getElementById('mTmp');tEl.innerHTML=t.temp_c+'°C';tEl.className='sv '+ct(+t.temp_c); const hkb=t.free_heap/1024;const hEl=document.getElementById('mH');hEl.innerHTML=hkb.toFixed(0)+'kB';hEl.className='sv '+ch(hkb); const mhkb=t.min_heap/1024;document.getElementById('mMH').innerHTML=mhkb.toFixed(0)+'kB'; - document.getElementById('mDw').innerHTML=t.sweep_dwell_ms+'ms'; + const mdw=document.getElementById('mDw'); + if(mdw)mdw.textContent=(t.jam_fixed===true||t.jam_fixed==='true')?'LOCKED':(String(t.sweep_dwell_ms??'')+' ms'); const h1=t.hop_count1||0,h2=t.hop_count2||0,dh=(h1-ph1+h2-ph2),rate=dt>0?(dh/dt).toFixed(0):0; ph1=h1;ph2=h2; document.getElementById('mH1').textContent=h1.toLocaleString(); @@ -1674,6 +1607,7 @@ static void handleTelemetry() { "\"sweep_span2\":%.2f," "\"sweep_steps2\":%u," "\"sweep_dwell_ms\":%lu," + "\"jam_fixed\":true," "\"radio1_status\":%d," "\"radio1_error\":\"%s\"," "\"radio1_freq\":%.4f," @@ -1699,13 +1633,13 @@ static void handleTelemetry() { (int)effDbm, (double)effWatts, (double)sweepFreq1, - (double)SWEEP_1_CENTER_MHZ, - (double)sweep1SpanMhz, - (unsigned)sweep1Steps, + (double)JAM_LOCK_FREQ_1_MHZ, + 2.0, + (unsigned)1, (double)sweepFreq2, - (double)SWEEP_2_CENTER_MHZ, - (double)sweep2SpanMhz, - (unsigned)sweep2Steps, + (double)JAM_LOCK_FREQ_2_MHZ, + 2.0, + (unsigned)1, (unsigned long)sweepDwellMs, (int)radio1Status, err1.c_str(), @@ -1815,18 +1749,7 @@ static void handleSweepSettings() { preferences.putFloat("sweep1Span", sweep1SpanMhz); preferences.putFloat("sweep2Span", sweep2SpanMhz); - if (radio1Status >= 1) { - sweepStep1 = 0; - buildSweepTable(radio1, CC1101_1_CS, sweepTable1, sweep1Steps, SWEEP_1_CENTER_MHZ, sweep1SpanMhz); - } - if (radio2Status >= 1) { - sweepStep2 = 0; - buildSweepTable(radio2, CC1101_2_CS, sweepTable2, sweep2Steps, SWEEP_2_CENTER_MHZ, sweep2SpanMhz); - } - - logLine("[SWEEP] dwell=" + String(sweepDwellMs) + "ms steps=" + - String(sweep1Steps) + "/" + String(sweep2Steps) + - " span=" + String(sweep1SpanMhz,2) + "/" + String(sweep2SpanMhz,2) + "MHz"); + logLine("[SWEEP] prefs saved (fixed-carrier jam ignores dwell/steps/span)"); } server.send(200, "application/json; charset=utf-8", "{\"success\":true,\"dwell_ms\":" + String(sweepDwellMs) + @@ -2149,43 +2072,6 @@ void setup() { delay(800); // hold boot result on display briefly before switching to live pages } -// Advance one radio to the next sweep frequency using cached VCO calibration. -// Bypasses the ~720µs auto-calibration dead time on every hop. -static void tickSweepFast(uint8_t csPin, uint8_t& step, uint8_t steps, - SweepStepCache* table, uint32_t& lastMs, float& curFreq, - uint32_t& hopCnt) { - const uint32_t now = millis(); - if (now - lastMs < sweepDwellMs) return; - lastMs = now; - - // Jump to IDLE to safely change registers - spiStrobe(csPin, 0x36); // SIDLE - - if (step >= 100) step = 0; // bounds check - - // Write cached FREQ registers (0x0D, 0x0E, 0x0F) - spiWriteReg(csPin, 0x0D, table[step].freqRegs[0]); - spiWriteReg(csPin, 0x0E, table[step].freqRegs[1]); - spiWriteReg(csPin, 0x0F, table[step].freqRegs[2]); - - // Write cached FSCAL registers (0x23, 0x24, 0x25) - spiWriteReg(csPin, 0x23, table[step].fscalRegs[0]); - spiWriteReg(csPin, 0x24, table[step].fscalRegs[1]); - spiWriteReg(csPin, 0x25, table[step].fscalRegs[2]); - - // Disable auto-calibration before transmitting (MCSM0 register 0x18, bits 5:4 = 00) - // RadioLib defaults this to 0x18 (0001 1000) which is 01 (calibrate from IDLE to TX). - // We overwrite it to 0x08 (0000 1000) to never auto-calibrate. - spiWriteReg(csPin, 0x18, 0x08); - - // Jump straight to TX without auto-cal - spiStrobe(csPin, 0x35); // STX - - curFreq = table[step].freqMhz; - hopCnt++; - step = (step + 1) % steps; -} - void loop() { espNowTick(); ArduinoOTA.handle(); @@ -2271,14 +2157,8 @@ void loop() { Serial.flush(); } - // Frequency sweep — hop both radios across their bands while jamming - if (jammingEnabled) { - if (radio1Status == 2) - tickSweepFast(CC1101_1_CS, sweepStep1, sweep1Steps, sweepTable1, lastSweep1Ms, sweepFreq1, hopCount1); - if (radio2Status == 2) - tickSweepFast(CC1101_2_CS, sweepStep2, sweep2Steps, sweepTable2, lastSweep2Ms, sweepFreq2, hopCount2); - } - + // Fixed carriers — no sweep; sweepFreq1/2 stay at JAM_LOCK_* for telemetry/UI + // Handle serial input for debugging if (Serial.available()) { String cmd = Serial.readStringUntil('\n'); @@ -2290,8 +2170,8 @@ void loop() { } else if (cmd == "status") { Serial.println("Jamming: " + String(jammingEnabled ? "ON" : "OFF")); Serial.println("Power: " + String(jamPower) + " dBm"); - Serial.println("Radio 1 (300-320 MHz): " + String(jammingEnabled ? "TRANSMITTING" : "STANDBY")); - Serial.println("Radio 2 (390-436 MHz): " + String(jammingEnabled ? "TRANSMITTING" : "STANDBY")); + Serial.println("Radio 1 (315 MHz fixed): " + String(jammingEnabled ? "TRANSMITTING" : "STANDBY")); + Serial.println("Radio 2 (433.92 MHz fixed): " + String(jammingEnabled ? "TRANSMITTING" : "STANDBY")); } } }