Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/core/menu_items/WifiMenu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
// 64bit: https://github.com/9dl/Bruce-C2/releases/download/v1.0/BruceC2_windows_amd64.exe
#include "modules/wifi/socks4_proxy.h"
#include "modules/wifi/tcp_utils.h"
#include "modules/wifi/wifi_analyzer.h"

// global toggle - controls whether scanNetworks includes hidden SSIDs
bool showHiddenNetworks = false;
Expand Down Expand Up @@ -91,6 +92,7 @@ void WifiMenu::optionsMenu() {
options.push_back({"WiFi Pass Recovery", wifi_recover_menu});
#endif

options.push_back({"WiFi Analyzer", wifiAnalyzerMenu});
options.push_back({"Config", [this]() { configMenu(); }});

addOptionToMainMenu();
Expand Down
80 changes: 56 additions & 24 deletions src/modules/rf/rf_listen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,38 @@

#include "../others/audio.h"

volatile unsigned long lastMicros = 0;
volatile unsigned long pulseMicros = 0;
volatile float ___frequency = 0;
volatile unsigned long lastEdgeMicros = 0;
volatile unsigned long pulseDuration = 0;
volatile bool newPulse = false;

// MUST be fully IRAM-safe: no digitalRead() (lives in flash) and no access to C++ objects
// like bruceConfigPins from here. On GDO0 the noise can fire this thousands of times/s; if it
// runs while the flash cache is busy (e.g. display/font reads) the chip crashes/reboots.
void IRAM_ATTR onPulse() {
static bool wasHigh = false;
unsigned long now = micros();
unsigned long period = now - lastEdgeMicros;
lastEdgeMicros = now;

if (digitalRead(bruceConfigPins.CC1101_bus.io0)) {
pulseDuration = now - lastMicros;
___frequency = 1000000.0 / pulseDuration;
// Measure the period between rising edges. Drop sub-20us glitches and >1s gaps so a noise
// burst doesn't keep newPulse pinned and the displayed value stays meaningful.
if (period >= 20 && period <= 1000000UL) {
pulseDuration = period;
newPulse = true;
wasHigh = true;
} else if (wasHigh) {
lastMicros = now;
wasHigh = false;
}
}

// Beep through the EXACT same path as the working DTMF Tones.js script:
// audio.tone -> (HAS_NS4168_SPKR) serialCli.parse("tone f d") -> toneCallback -> playTone.
// The key detail learned from that script: the I2S speaker needs a long tone (~500ms) to be
// audible - short beeps get swallowed by the DMA buffer before it drains.
static void rf_listen_beep(unsigned int hz, unsigned long ms) {
#if defined(BUZZ_PIN)
tone(BUZZ_PIN, hz, ms); // non-blocking buzzer
#else
serialCli.parse("tone " + String(hz) + " " + String(ms));
#endif
}

void rf_listen() {
float freq = 433.92;
float last_freq = -1;
Expand Down Expand Up @@ -60,38 +71,59 @@ void rf_listen() {
ELECHOUSE_cc1101.setRxBW(58);
ELECHOUSE_cc1101.setModulation(2);
ELECHOUSE_cc1101.setDcFilterOff(true);
attachInterrupt(digitalPinToInterrupt(bruceConfigPins.CC1101_bus.io0), onPulse, CHANGE);
// Short confirmation beep so you know listening has started.
rf_listen_beep(1000, 500);

// Seed the timestamp so the first measured period isn't a bogus "time since boot" value
// (that was the 0.03 Hz reading).
lastEdgeMicros = micros();
newPulse = false;
attachInterrupt(digitalPinToInterrupt(bruceConfigPins.CC1101_bus.io0), onPulse, RISING);
displayRedStripe("Listening...", getComplementaryColor2(bruceConfig.priColor), bruceConfig.priColor);

unsigned long lastPulseTime = millis();
unsigned long lastTone = 0;
bool pulseActive = false;

// Only repaint the stripe when the text actually changes. Redrawing every loop iteration
// is a full SPI screen draw that (together with the GDO0 interrupt storm) freezes the UI.
String lastStripe = "";
auto showStripe = [&](const String &text) {
if (text == lastStripe) return;
lastStripe = text;
displayRedStripe(text, getComplementaryColor2(bruceConfig.priColor), bruceConfig.priColor);
};

while (check(EscPress)) { delay(10); }

showStripe("Waiting for a pulse");

while (!check(EscPress)) {
displayRedStripe(
"Waiting for a pulse", getComplementaryColor2(bruceConfig.priColor), bruceConfig.priColor
);
if (newPulse) {
newPulse = false;
lastPulseTime = millis();
pulseActive = true;
String pulseText = String("Freq: ") + String(___frequency, 2) + String(" Hz");
displayRedStripe(pulseText, getComplementaryColor2(bruceConfig.priColor), bruceConfig.priColor);
#if defined(BUZZ_PIN)
tone(BUZZ_PIN, ___frequency, pulseDuration);
#elif defined(HAS_NS4168_SPKR)
playTone(___frequency, pulseDuration, 0);
#endif
// Compute frequency here (out of the ISR) to keep the interrupt fast.
unsigned long dur = pulseDuration;
float freqHz = dur ? (1000000.0f / dur) : 0.0f;
showStripe(String("Freq: ") + String(freqHz, 2) + String(" Hz"));
// Audible feedback at the detected pulse frequency, via the same proven path as
// dtmf.js. The I2S speaker needs ~500ms to be audible, and playTone BLOCKS for that
// time, so throttle to 700ms so a continuous signal doesn't make the listener feel
// stuck.
if (millis() - lastTone > 700) {
lastTone = millis();
rf_listen_beep((unsigned int)constrain(freqHz, 100.0f, 8000.0f), 500);
}
}

if (pulseActive && millis() - lastPulseTime > 3000) {
pulseActive = false;
displayRedStripe("No signal", getComplementaryColor2(bruceConfig.priColor), bruceConfig.priColor);
showStripe("Waiting for a pulse");
}

if (check(EscPress)) break;
if (check(SelPress)) break;
delay(5); // feed the watchdog and let button/UI handling breathe
}

detachInterrupt(digitalPinToInterrupt(bruceConfigPins.CC1101_bus.io0));
Expand Down
Loading
Loading