Final review fixes: capture state, stopJamming idempotency, code cleanup
Critical bug fixes: - stopJamming() was returning early when jammingEnabled=false, leaving radios in unknown state and breaking capture when jamming was off - stopJamming() now idempotent: always stops noise timer (+ nulls pointer), drives GDO0 low, puts transmitting radios to standby — safe to call anytime - Add capPrevJamming flag saved before stopJamming() clears jammingEnabled; used by stopCapture() and buffer-full handler to correctly restart jamming after capture/replay sessions end Code quality: - capAnalyze() called twice per /api/capture/status request — now called once - Remove unused JAM_NOISE_PATTERN_LEN define (leftover from LEDC era) - Fix stale "LEDC 120 kHz square wave" comment in startJamming() - Web UI: show rec_bits (final stored count) instead of capIdx in Bits cell; progress bar shows 100% when in RECORDED/REPLAYING state Made-with: Cursor
This commit is contained in:
@@ -30,8 +30,6 @@
|
||||
// { -30, -20, -15, -10, 0, 5, 7, 10 } dBm
|
||||
#define JAM_POWER_LEVELS 8
|
||||
#define DEFAULT_JAM_POWER_IDX 7 // index into power table (7 = 10 dBm, max)
|
||||
#define JAM_NOISE_PATTERN_LEN 64
|
||||
|
||||
// External amplifier gain in dB (used only for display — does not affect CC1101 output)
|
||||
#define DEFAULT_AMP_GAIN_DB 20
|
||||
|
||||
|
||||
72
src/main.cpp
72
src/main.cpp
@@ -176,9 +176,10 @@ static uint32_t capRecBits = 0; // bits stored after recording
|
||||
static float capFreq = 315.0f; // frequency at capture time
|
||||
static uint8_t capRadioNum = 1; // 1 or 2
|
||||
static gpio_num_t capGdoPin = (gpio_num_t)CC1101_1_GDO0;
|
||||
static hw_timer_t* capTimer = nullptr;
|
||||
static volatile uint32_t capTransitions = 0; // edge count — used to detect live signal
|
||||
static hw_timer_t* capTimer = nullptr;
|
||||
static volatile uint32_t capTransitions = 0; // edge count — used to detect live signal
|
||||
static bool capSigNotified = false; // fire OLED notification only once per session
|
||||
static bool capPrevJamming = false; // jammingEnabled state saved before capture pauses it
|
||||
|
||||
// ─── Capture/replay ISRs ──────────────────────────────────────────────────────
|
||||
static void IRAM_ATTR capRecordISR() {
|
||||
@@ -214,6 +215,7 @@ static void capTimerStop() {
|
||||
}
|
||||
|
||||
static void startCapture(float freq, uint8_t radioNum) {
|
||||
capPrevJamming = jammingEnabled; // save before stopJamming() clears it
|
||||
stopJamming();
|
||||
|
||||
capFreq = freq;
|
||||
@@ -250,6 +252,7 @@ static void startCapture(float freq, uint8_t radioNum) {
|
||||
static void startReplay(uint8_t radioNum) {
|
||||
if (capRecBits == 0) { logLine("[CAP] Nothing captured to replay"); return; }
|
||||
|
||||
capPrevJamming = jammingEnabled; // save before stopJamming() clears it
|
||||
stopJamming();
|
||||
|
||||
capRadioNum = radioNum;
|
||||
@@ -289,10 +292,13 @@ static void stopCapture() {
|
||||
logLine("[CAP] Replay stopped");
|
||||
oledNotify("REPLAY", "STOPPED", 2500);
|
||||
}
|
||||
// Restore pin directions then restart jamming
|
||||
// Restore pin direction then restart jamming if it was active before capture
|
||||
gpio_set_direction(capGdoPin, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level(capGdoPin, 0);
|
||||
if (jammingEnabled) startJamming();
|
||||
if (capPrevJamming) {
|
||||
jammingEnabled = true;
|
||||
startJamming();
|
||||
}
|
||||
}
|
||||
|
||||
// Simple signal analysis — counts transitions to estimate original bitrate
|
||||
@@ -401,8 +407,8 @@ static void startJamming() {
|
||||
int stTx1 = RADIOLIB_ERR_NONE;
|
||||
int stTx2 = RADIOLIB_ERR_NONE;
|
||||
|
||||
// Start async direct TX — GDO0 becomes serial-data input, LEDC drives it with
|
||||
// a 120 kHz square wave producing ±120 kHz FM noise instead of narrow-band CW.
|
||||
// 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).
|
||||
noiseGenStart();
|
||||
|
||||
if (radio1Status == 1) {
|
||||
@@ -439,45 +445,46 @@ static void startJamming() {
|
||||
}
|
||||
}
|
||||
|
||||
// Stop jamming
|
||||
// Stop jamming — idempotent, safe to call at any time including from capture code.
|
||||
// Always brings GDO0 pins and noise timer to a known-safe state regardless of
|
||||
// whether jammingEnabled was true. Only logs if something was actually active.
|
||||
static void stopJamming() {
|
||||
if (!jammingEnabled) {
|
||||
logLine("[JAM] Not currently jamming, ignoring stop request");
|
||||
return;
|
||||
const bool wasActive = jammingEnabled;
|
||||
|
||||
// Always stop noise timer first — prevents ISR touching GDO0 during standby
|
||||
if (s_noiseTimer) {
|
||||
timerAlarmDisable(s_noiseTimer);
|
||||
timerDetachInterrupt(s_noiseTimer);
|
||||
timerEnd(s_noiseTimer);
|
||||
s_noiseTimer = nullptr;
|
||||
}
|
||||
|
||||
logLine("[JAM] Stopping jamming system");
|
||||
|
||||
// Stop transmission on both radios
|
||||
if (radio1Status == 2) { // Transmitting
|
||||
gpio_set_level((gpio_num_t)CC1101_1_GDO0, 0);
|
||||
gpio_set_level((gpio_num_t)CC1101_2_GDO0, 0);
|
||||
|
||||
if (radio1Status == 2) {
|
||||
int st1 = radio1.standby();
|
||||
if (st1 != RADIOLIB_ERR_NONE) {
|
||||
radio1Error = "Standby failed: " + String(st1);
|
||||
logLine("[R1] standby failed: " + String(st1));
|
||||
} else {
|
||||
radio1Status = 1; // Initialized but not transmitting
|
||||
radio1Error = "";
|
||||
radio1Status = 1;
|
||||
radio1Error = "";
|
||||
}
|
||||
}
|
||||
|
||||
if (radio2Status == 2) { // Transmitting
|
||||
|
||||
if (radio2Status == 2) {
|
||||
int st2 = radio2.standby();
|
||||
if (st2 != RADIOLIB_ERR_NONE) {
|
||||
radio2Error = "Standby failed: " + String(st2);
|
||||
logLine("[R2] standby failed: " + String(st2));
|
||||
} else {
|
||||
radio2Status = 1; // Initialized but not transmitting
|
||||
radio2Error = "";
|
||||
radio2Status = 1;
|
||||
radio2Error = "";
|
||||
}
|
||||
}
|
||||
|
||||
// Stop noise timer and drive GDO0 pins low
|
||||
if (s_noiseTimer) { timerAlarmDisable(s_noiseTimer); timerDetachInterrupt(s_noiseTimer); }
|
||||
gpio_set_level((gpio_num_t)CC1101_1_GDO0, 0);
|
||||
gpio_set_level((gpio_num_t)CC1101_2_GDO0, 0);
|
||||
|
||||
jammingEnabled = false;
|
||||
logLine("[JAM] Jamming stopped");
|
||||
if (wasActive) logLine("[JAM] Jamming stopped");
|
||||
}
|
||||
|
||||
// ─── OLED functions ──────────────────────────────────────────────────────────
|
||||
@@ -1295,13 +1302,13 @@ function capDrawWave(){
|
||||
|
||||
function capSetStatus(label,d){
|
||||
document.getElementById('capStat').textContent=label;
|
||||
if(d&&d.bits!==undefined)document.getElementById('capBits').textContent=d.bits.toLocaleString()+' bits';
|
||||
const dispBits=d?(d.rec_bits||d.bits):0;if(dispBits!==undefined)document.getElementById('capBits').textContent=dispBits.toLocaleString()+' bits';
|
||||
if(d&&d.dur_ms)document.getElementById('capDur').textContent=(d.dur_ms/1000).toFixed(2)+'s';
|
||||
if(d&&d.est_bps)document.getElementById('capBps').textContent=d.est_bps.toLocaleString()+' bps';
|
||||
if(d&&d.duty_pct!==undefined)document.getElementById('capDuty').textContent=d.duty_pct+'%';
|
||||
if(d&&d.freq)document.getElementById('capRecFreq').textContent=d.freq.toFixed(3)+' MHz';
|
||||
const pct=d&&d.buf_bits?Math.round(d.bits*100/d.buf_bits):0;
|
||||
document.getElementById('capProg').style.width=pct+'%';
|
||||
const pct=d?(d.mode===1&&d.buf_bits?Math.round(d.bits*100/d.buf_bits):(d.mode===3&&d.rec_bits?Math.round(d.bits*100/d.rec_bits):100)):0;
|
||||
document.getElementById('capProg').style.width=(d&&(d.mode===2||d.mode===3)?100:pct)+'%';
|
||||
}
|
||||
|
||||
let capPollTimer=null;
|
||||
@@ -1543,7 +1550,8 @@ static void handleCaptureStatus() {
|
||||
json += "\"pct\":" + String((uint32_t)(capIdx * 100UL / (CAP_BUF_BYTES * 8))) + ",";
|
||||
json += "\"freq\":" + String(capFreq, 3);
|
||||
if (capMode == CapMode::RECORDED || capMode == CapMode::REPLAYING) {
|
||||
json += "," + capAnalyze().substring(1, capAnalyze().length() - 1); // merge JSON fields
|
||||
const String analysis = capAnalyze();
|
||||
json += "," + analysis.substring(1, analysis.length() - 1); // merge JSON fields
|
||||
}
|
||||
json += "}";
|
||||
server.send(200, "application/json; charset=utf-8", json);
|
||||
@@ -1806,7 +1814,7 @@ void loop() {
|
||||
logLine("[CAP] Buffer full: " + String(capRecBits) + " bits (" +
|
||||
String(capRecBits * 1000 / CAP_SAMPLE_HZ) + " ms) captured");
|
||||
oledNotify("CAPTURED", (String(capRecBits * 1000 / CAP_SAMPLE_HZ) + "ms").c_str());
|
||||
if (jammingEnabled) startJamming();
|
||||
if (capPrevJamming) { jammingEnabled = true; startJamming(); }
|
||||
}
|
||||
|
||||
// Auto-reinit watchdog: if jamming should be active but a radio failed, retry every 30s
|
||||
|
||||
Reference in New Issue
Block a user