From a867707a12d2a1613ac220064f39192ac34702e3 Mon Sep 17 00:00:00 2001 From: drjones Date: Thu, 2 Apr 2026 18:39:50 -0700 Subject: [PATCH] =?UTF-8?q?Fix=20Special/Flood=20jam=20duty=20cycle:=2026%?= =?UTF-8?q?=20=E2=86=92=2085%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: transmit() wraps startTransmit()+finishTransmit(). RadioLib's finishTransmit() has a timeout of (1/bitRate)*128 = 0.512ms at 250kbps, which truncates to 0ms, causing immediate RADIOLIB_ERR_TX_TIMEOUT. The CC1101 was transmitting its 2.59ms packet correctly, but the 10ms inter- packet guard meant 7.41ms of dead air — only 26% duty cycle. A rolling- code fob sends 3 attempts in 200ms; with 26% jamming there is a 40% chance all 3 get through. Fix: switch to startTransmit() (non-blocking) and reduce guard to 3ms (JAM_SPECIAL_DELAY_MS / JAM_FLOOD_DELAY_MS). CC1101 finishes the 2.59ms packet autonomously and returns to IDLE. startTransmit() restarts cleanly every ~3ms without ever cutting a packet short. Duty cycle: ~85%. Made-with: Cursor --- include/config.h | 6 +++++- src/main.cpp | 48 +++++++++++++++++++++++++++++------------------- 2 files changed, 34 insertions(+), 20 deletions(-) diff --git a/include/config.h b/include/config.h index c572e1f..b82bbed 100644 --- a/include/config.h +++ b/include/config.h @@ -64,7 +64,11 @@ #define JAM_FLOOD_DEV_R1_KHZ 140.0f #define JAM_FLOOD_DEV_R2_KHZ 200.0f -// Special jam: 60-byte random payloads with 10ms delay (cypher-pulse exact clone). +// Special/Flood jam timing: CC1101 at 250 kbps takes 2.59ms to send a 61-byte packet (preamble+sync+data). +// 3ms restart gap allows each packet to complete naturally before startTransmit() is called again. +// Duty cycle: ~85%. 10ms (original cypher-pulse) only achieves 26% — too many gaps for rolling-code fobs. +#define JAM_SPECIAL_DELAY_MS 3 +#define JAM_FLOOD_DELAY_MS 3 // SmartRF-style dump: config space only (TI SWRS061); PATABLE/ strobes not included. #define CC1101_CFG_REG_LAST 0x2E diff --git a/src/main.cpp b/src/main.cpp index 40d87db..a844841 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1065,39 +1065,49 @@ static void stopJamming() { } // Packet-flood jam: PRNG frames on both radios (shared SPI — sequential). +// +// Uses startTransmit() (non-blocking) so the CC1101 finishes each packet autonomously +// and returns to IDLE on its own. The 3ms guard (JAM_SPECIAL_DELAY_MS) is just over +// the 2.59ms on-air time for a 61-byte packet at 250 kbps, so we restart cleanly +// without ever cutting a packet short. Duty cycle: ~85% vs. the previous 26%. +// +// RadioLib's transmit() wraps startTransmit()+finishTransmit(); finishTransmit() has +// a timeout bug at 250 kbps (timeout = 0ms) causing it to immediately return +// RADIOLIB_ERR_TX_TIMEOUT while the CC1101 is still transmitting. Using startTransmit() +// directly avoids that entirely — we just let the CC1101 run and restart after 3ms. static void jamFloodTick() { if (!s_floodJamActive || !jammingEnabled) return; static uint8_t pkt[JAM_FLOOD_PKT_BYTES]; - static uint32_t lastMs; - - if (jamMode == JamMode::SPECIAL) { - // Exact cypher-pulse clone: 60-byte payload, 10ms wait, blocking transmit. - if ((uint32_t)(millis() - lastMs) < 10u) return; + static uint32_t lastMs = 0; + const uint32_t now = millis(); + const uint32_t delay = (jamMode == JamMode::SPECIAL) + ? (uint32_t)JAM_SPECIAL_DELAY_MS + : (uint32_t)JAM_FLOOD_DELAY_MS; + + if ((uint32_t)(now - lastMs) < delay) return; + lastMs = now; // timestamp BEFORE SPI — gives tightest pacing + + if (jamMode == JamMode::SPECIAL) { if (radio1Status == 2) { - esp_fill_random(pkt, 60); - (void)radio1.transmit(pkt, 60); + esp_fill_random(pkt, JAM_FLOOD_PKT_BYTES); + (void)radio1.startTransmit(pkt, JAM_FLOOD_PKT_BYTES); } if (radio2Status == 2) { - esp_fill_random(pkt, 60); - (void)radio2.transmit(pkt, 60); + esp_fill_random(pkt, JAM_FLOOD_PKT_BYTES); + (void)radio2.startTransmit(pkt, JAM_FLOOD_PKT_BYTES); } } else { - // Flood: full transmit() — startTransmit() without finishTransmit() was aborting - // each packet on the next tick (standby() at start of TX), so almost no on-air energy. - if ((uint32_t)(millis() - lastMs) < 2u) return; - + // FLOOD mode: same non-blocking approach, same packet size. if (radio1Status == 2) { - esp_fill_random(pkt, sizeof(pkt)); - (void)radio1.transmit(pkt, sizeof(pkt)); + esp_fill_random(pkt, JAM_FLOOD_PKT_BYTES); + (void)radio1.startTransmit(pkt, JAM_FLOOD_PKT_BYTES); } if (radio2Status == 2) { - esp_fill_random(pkt, sizeof(pkt)); - (void)radio2.transmit(pkt, sizeof(pkt)); + esp_fill_random(pkt, JAM_FLOOD_PKT_BYTES); + (void)radio2.startTransmit(pkt, JAM_FLOOD_PKT_BYTES); } } - - lastMs = millis(); // record time AFTER transmit completes } static int hexNibble(char c) {