Add signal detection notification and capture-aware OLED modes
- capRecordISR: counts bit transitions to detect live RF activity
- loop(): fires oledNotify("SIGNAL!", "CAUGHT -- PRESS STOP") once per
recording session when transition count exceeds 80 (a few ms of any
OOK/FSK burst), then logs bit index and transition count
- oledDrawStatus: distinct header states for RECORDING (blinking box),
REPLAYING (solid inverted), JAMMING ACTIVE, and STANDBY
- Blue zone during capture shows frequency, radio, live progress bar,
elapsed seconds, transition count; during replay shows bit count + loop indicator
Made-with: Cursor
This commit is contained in:
138
src/main.cpp
138
src/main.cpp
@@ -177,13 +177,19 @@ 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 bool capSigNotified = false; // fire OLED notification only once per session
|
||||
|
||||
// ─── Capture/replay ISRs ──────────────────────────────────────────────────────
|
||||
static void IRAM_ATTR capRecordISR() {
|
||||
const uint32_t i = capIdx;
|
||||
if (i >= (uint32_t)(CAP_BUF_BYTES * 8)) { capBufFull = true; return; }
|
||||
// Direct register read — 1-2 CPU cycles, safe from ISR
|
||||
const uint8_t bit = (uint8_t)((REG_READ(GPIO_IN_REG) >> capGdoPin) & 1u);
|
||||
// Count transitions — cheap edge detection without extra storage
|
||||
if (i > 0) {
|
||||
const uint8_t prev = (capBuf[(i-1) >> 3] >> ((i-1) & 7)) & 1u;
|
||||
if (bit != prev) capTransitions++;
|
||||
}
|
||||
if (bit) capBuf[i >> 3] |= (1u << (i & 7));
|
||||
else capBuf[i >> 3] &= ~(1u << (i & 7));
|
||||
capIdx = i + 1;
|
||||
@@ -213,9 +219,11 @@ static void startCapture(float freq, uint8_t radioNum) {
|
||||
capFreq = freq;
|
||||
capRadioNum = radioNum;
|
||||
capGdoPin = (radioNum == 1) ? (gpio_num_t)CC1101_1_GDO0 : (gpio_num_t)CC1101_2_GDO0;
|
||||
capIdx = 0;
|
||||
capBufFull = false;
|
||||
capRecBits = 0;
|
||||
capIdx = 0;
|
||||
capBufFull = false;
|
||||
capRecBits = 0;
|
||||
capTransitions = 0;
|
||||
capSigNotified = false;
|
||||
memset(capBuf, 0, sizeof(capBuf));
|
||||
|
||||
CC1101& radio = (radioNum == 1) ? radio1 : radio2;
|
||||
@@ -521,10 +529,24 @@ static void oledDrawStatus() {
|
||||
const bool jam = jammingEnabled;
|
||||
const bool r1 = (radio1Status == 2);
|
||||
const bool r2 = (radio2Status == 2);
|
||||
const CapMode cm = capMode;
|
||||
|
||||
// Yellow zone header — inverted box when jamming
|
||||
// Yellow zone header
|
||||
u8g2.setFont(u8g2_font_6x10_tf);
|
||||
if (jam) {
|
||||
if (cm == CapMode::RECORDING) {
|
||||
// Flashing border effect — blink every ~500 ms using bit 9 of millis()
|
||||
if (millis() & 512) {
|
||||
u8g2.drawBox(0, 0, 128, 13);
|
||||
u8g2.setDrawColor(0);
|
||||
}
|
||||
u8g2.drawStr(2, 10, ">> RECORDING <<");
|
||||
u8g2.setDrawColor(1);
|
||||
} else if (cm == CapMode::REPLAYING) {
|
||||
u8g2.drawBox(0, 0, 128, 13);
|
||||
u8g2.setDrawColor(0);
|
||||
u8g2.drawStr(2, 10, ">> REPLAYING <<");
|
||||
u8g2.setDrawColor(1);
|
||||
} else if (jam) {
|
||||
u8g2.drawBox(0, 0, 110, 13);
|
||||
u8g2.setDrawColor(0);
|
||||
u8g2.drawStr(2, 10, ">> JAMMING ACTIVE <<");
|
||||
@@ -534,46 +556,78 @@ static void oledDrawStatus() {
|
||||
}
|
||||
oledPageDots(0);
|
||||
|
||||
// Blue zone — 5x7 font, 4 rows at y=22,32,43,54 then 5th at y=63
|
||||
// Blue zone — 5x7 font
|
||||
u8g2.setFont(u8g2_font_5x7_tf);
|
||||
const uint8_t nW = waveFrame;
|
||||
|
||||
// Row 1: ANT1
|
||||
if (r1) {
|
||||
char buf[20];
|
||||
snprintf(buf, sizeof(buf), "1: %.3f MHz", (double)sweepFreq1);
|
||||
if (cm == CapMode::RECORDING || cm == CapMode::REPLAYING) {
|
||||
// Show capture/replay status instead of sweep info
|
||||
char buf[24];
|
||||
snprintf(buf, sizeof(buf), "%.3f MHz R%u",
|
||||
(double)capFreq, (unsigned)capRadioNum);
|
||||
u8g2.drawStr(0, 24, buf);
|
||||
oledDrawWaves(101, 19, nW);
|
||||
} else {
|
||||
u8g2.drawStr(0, 24, "1: [OFFLINE]");
|
||||
}
|
||||
|
||||
// Row 2: ANT2
|
||||
if (r2) {
|
||||
char buf[20];
|
||||
snprintf(buf, sizeof(buf), "2: %.3f MHz", (double)sweepFreq2);
|
||||
u8g2.drawStr(0, 33, buf);
|
||||
oledDrawWaves(101, 28, nW);
|
||||
} else {
|
||||
u8g2.drawStr(0, 33, "2: [OFFLINE]");
|
||||
}
|
||||
// Progress bar for recording
|
||||
if (cm == CapMode::RECORDING) {
|
||||
const uint32_t pct = capIdx * 100 / (CAP_BUF_BYTES * 8);
|
||||
u8g2.drawFrame(0, 26, 128, 5);
|
||||
u8g2.drawBox(0, 26, (uint8_t)(pct * 128 / 100), 5);
|
||||
snprintf(buf, sizeof(buf), "%lus / %us %lu tr",
|
||||
(unsigned long)(capIdx / CAP_SAMPLE_HZ),
|
||||
(unsigned)CAP_DURATION_S,
|
||||
(unsigned long)capTransitions);
|
||||
} else {
|
||||
// Replaying — show loop position
|
||||
const uint32_t pct = capRecBits ? capIdx * 100 / capRecBits : 0;
|
||||
u8g2.drawFrame(0, 26, 128, 5);
|
||||
u8g2.drawBox(0, 26, (uint8_t)(pct * 128 / 100), 5);
|
||||
snprintf(buf, sizeof(buf), "%lu bits looping",
|
||||
(unsigned long)capRecBits);
|
||||
}
|
||||
u8g2.drawStr(0, 40, buf);
|
||||
|
||||
// Row 3: power
|
||||
{
|
||||
char buf[28];
|
||||
snprintf(buf, sizeof(buf), "TX %d+%d=%ddBm",
|
||||
(int)jamPower, (int)ampGainDb, (int)jamPower + (int)ampGainDb);
|
||||
u8g2.drawStr(0, 44, buf);
|
||||
}
|
||||
|
||||
// Row 4: temp + heap OR FULL TX badge
|
||||
if (jam && r1 && r2) {
|
||||
u8g2.drawStr(0, 55, "[ FULL DUAL-BAND TX ]");
|
||||
} else {
|
||||
char buf[28];
|
||||
snprintf(buf, sizeof(buf), "%.1fC %lukB",
|
||||
(double)temperatureRead(), (unsigned long)(ESP.getFreeHeap() / 1024));
|
||||
u8g2.drawStr(0, 55, buf);
|
||||
} else {
|
||||
// Normal jamming / standby display
|
||||
// Row 1: ANT1
|
||||
if (r1) {
|
||||
char buf[20];
|
||||
snprintf(buf, sizeof(buf), "1: %.3f MHz", (double)sweepFreq1);
|
||||
u8g2.drawStr(0, 24, buf);
|
||||
oledDrawWaves(101, 19, nW);
|
||||
} else {
|
||||
u8g2.drawStr(0, 24, "1: [OFFLINE]");
|
||||
}
|
||||
|
||||
// Row 2: ANT2
|
||||
if (r2) {
|
||||
char buf[20];
|
||||
snprintf(buf, sizeof(buf), "2: %.3f MHz", (double)sweepFreq2);
|
||||
u8g2.drawStr(0, 33, buf);
|
||||
oledDrawWaves(101, 28, nW);
|
||||
} else {
|
||||
u8g2.drawStr(0, 33, "2: [OFFLINE]");
|
||||
}
|
||||
|
||||
// Row 3: power
|
||||
{
|
||||
char buf[28];
|
||||
snprintf(buf, sizeof(buf), "TX %d+%d=%ddBm",
|
||||
(int)jamPower, (int)ampGainDb, (int)jamPower + (int)ampGainDb);
|
||||
u8g2.drawStr(0, 44, buf);
|
||||
}
|
||||
|
||||
// Row 4: temp + heap OR FULL TX badge
|
||||
if (jam && r1 && r2) {
|
||||
u8g2.drawStr(0, 55, "[ FULL DUAL-BAND TX ]");
|
||||
} else {
|
||||
char buf[28];
|
||||
snprintf(buf, sizeof(buf), "%.1fC %lukB",
|
||||
(double)temperatureRead(), (unsigned long)(ESP.getFreeHeap() / 1024));
|
||||
u8g2.drawStr(0, 55, buf);
|
||||
}
|
||||
}
|
||||
|
||||
// Row 5: uptime small
|
||||
@@ -1732,6 +1786,16 @@ void loop() {
|
||||
const uint32_t now = millis();
|
||||
|
||||
// Capture state machine — runs in main loop (ISR sets flags, loop acts on them)
|
||||
|
||||
// Signal-present detection: fire OLED "SIGNAL!" once per session when
|
||||
// the ISR has seen enough transitions to indicate a real RF burst.
|
||||
// 80 transitions ≈ a few milliseconds of OOK/FSK activity at any typical fob rate.
|
||||
if (capMode == CapMode::RECORDING && !capSigNotified && capTransitions > 80) {
|
||||
capSigNotified = true;
|
||||
oledNotify("SIGNAL!", "CAUGHT -- PRESS STOP", 3000);
|
||||
logLine("[CAP] Signal detected: " + String(capTransitions) + " transitions @ bit " + String(capIdx));
|
||||
}
|
||||
|
||||
if (capMode == CapMode::RECORDING && capBufFull) {
|
||||
capTimerStop();
|
||||
capRecBits = capIdx;
|
||||
|
||||
Reference in New Issue
Block a user