Performance and RF improvements: zero-alloc HTTP, PATABLE, ETag caching
Memory/Performance: - handleHealth: replaced String += with static snprintf buffer - handleCaptureStatus: replaced String += with static snprintf buffer, inlined capAnalyze() to eliminate intermediate String allocation - handleCaptureWave: replaced 256-iteration String += loop with static 1280-byte char buffer and snprintf — eliminates ~256 heap allocs per call - handleRoot: added ETag based on compile timestamp so the browser caches the ~15 KB HTML page and revalidates with If-None-Match; returns 304 Not Modified on subsequent loads instead of re-transmitting the full page RF Replay: - PATABLE OOK pulse shaping: before replay in OOK mode, writes PATABLE[0]=0x00 (full off) and PATABLE[1]=0xC0 (max +10 dBm) via SPI burst write. This gives the sharpest possible on/off keying contrast, eliminates residual carrier leakage during OFF bits, and maximizes effective replay range. Made-with: Cursor
This commit is contained in:
103
src/main.cpp
103
src/main.cpp
@@ -172,6 +172,7 @@ static void noiseGenStart();
|
||||
static void startJamming();
|
||||
static void stopJamming();
|
||||
static void oledNotify(const char* l1, const char* l2, uint32_t dur);
|
||||
static void spiWriteReg(uint8_t csPin, uint8_t reg, uint8_t val);
|
||||
|
||||
// ─── Noise generator globals (used by stopJamming before definition) ─────────
|
||||
static volatile uint32_t s_lfsr = 0xDEADBEEFu;
|
||||
@@ -286,10 +287,27 @@ static void startReplay(uint8_t radioNum) {
|
||||
capIdx = 0;
|
||||
|
||||
CC1101& radio = (radioNum == 1) ? radio1 : radio2;
|
||||
const uint8_t csPin = (radioNum == 1) ? CC1101_1_CS : CC1101_2_CS;
|
||||
radio.standby();
|
||||
radio.setOOK(capIsOOK);
|
||||
radio.setFrequency(capFreq);
|
||||
radio.setFrequencyDeviation(JAM_FREQ_DEV_KHZ);
|
||||
|
||||
// Configure PATABLE for maximum OOK contrast and TX power.
|
||||
// In OOK mode the CC1101 uses PATABLE[0] for "0" bits and PATABLE[1] for "1" bits.
|
||||
// 0x00 = full off, 0xC0 = max power (+10 dBm). This gives the sharpest on/off
|
||||
// keying and maximizes replay range by eliminating residual carrier leakage during OFF.
|
||||
if (capIsOOK) {
|
||||
spiWriteReg(csPin, 0x3E, 0x00); // PATABLE[0] = off
|
||||
spi.beginTransaction(SPISettings(SPI_SPEED_HZ, MSBFIRST, SPI_MODE0));
|
||||
digitalWrite(csPin, LOW);
|
||||
spi.transfer(0x7E); // Burst write PATABLE
|
||||
spi.transfer(0x00); // index 0: OFF
|
||||
spi.transfer(0xC0); // index 1: max power (+10 dBm)
|
||||
digitalWrite(csPin, HIGH);
|
||||
spi.endTransaction();
|
||||
}
|
||||
|
||||
radio.transmitDirectAsync(); // GDO0 becomes data input to CC1101
|
||||
|
||||
gpio_set_direction(capGdoPin, GPIO_MODE_OUTPUT);
|
||||
@@ -1439,7 +1457,14 @@ window.addEventListener('resize',capDrawWave);
|
||||
)HTML";
|
||||
|
||||
static void handleRoot() {
|
||||
server.sendHeader("Cache-Control", "no-store, max-age=0");
|
||||
// ETag based on compile timestamp — browser caches until next flash
|
||||
server.sendHeader("ETag", "\"" __DATE__ __TIME__ "\"");
|
||||
server.sendHeader("Cache-Control", "no-cache"); // revalidate via ETag, don't re-download
|
||||
if (server.hasHeader("If-None-Match") &&
|
||||
server.header("If-None-Match") == "\"" __DATE__ __TIME__ "\"") {
|
||||
server.send(304); // Not Modified — browser uses cached copy, saves ~15 KB
|
||||
return;
|
||||
}
|
||||
server.setContentLength(sizeof(kHtml) - 1);
|
||||
server.send(200, "text/html; charset=utf-8", "");
|
||||
server.sendContent(kHtml);
|
||||
@@ -1655,13 +1680,13 @@ static void handleAmpSettings() {
|
||||
}
|
||||
|
||||
static void handleHealth() {
|
||||
String json = "{";
|
||||
json += "\"ok\":true,";
|
||||
json += "\"uptime_ms\":" + String(millis() - uptimeStart) + ",";
|
||||
json += "\"heap\":" + String(ESP.getFreeHeap()) + ",";
|
||||
json += "\"ap_clients\":" + String(WiFi.softAPgetStationNum());
|
||||
json += "}";
|
||||
server.send(200, "application/json; charset=utf-8", json);
|
||||
static char buf[128];
|
||||
snprintf(buf, sizeof(buf),
|
||||
"{\"ok\":true,\"uptime_ms\":%lu,\"heap\":%lu,\"ap_clients\":%d}",
|
||||
(unsigned long)(millis() - uptimeStart),
|
||||
(unsigned long)ESP.getFreeHeap(),
|
||||
(int)WiFi.softAPgetStationNum());
|
||||
server.send(200, "application/json; charset=utf-8", buf);
|
||||
}
|
||||
|
||||
// ─── Capture / replay HTTP handlers ──────────────────────────────────────────
|
||||
@@ -1689,30 +1714,52 @@ static void handleCaptureReplay() {
|
||||
}
|
||||
|
||||
static void handleCaptureStatus() {
|
||||
const String modeStr[] = {"idle","recording","recorded","replaying"};
|
||||
static const char* const modeStr[] = {"idle","recording","recorded","replaying"};
|
||||
const uint8_t m = (uint8_t)capMode;
|
||||
String json = "{";
|
||||
json += "\"mode\":" + String(m) + ",";
|
||||
json += "\"mode_str\":\"" + modeStr[m < 4 ? m : 0] + "\",";
|
||||
json += "\"bits\":" + String(capIdx) + ",";
|
||||
json += "\"buf_bits\":" + String(CAP_BUF_BYTES * 8) + ",";
|
||||
json += "\"rec_bits\":" + String(capRecBits) + ",";
|
||||
json += "\"pct\":" + String((uint32_t)(capIdx * 100UL / (CAP_BUF_BYTES * 8))) + ",";
|
||||
json += "\"freq\":" + String(capFreq, 3);
|
||||
static char buf[512];
|
||||
int n = snprintf(buf, sizeof(buf),
|
||||
"{\"mode\":%u,\"mode_str\":\"%s\","
|
||||
"\"bits\":%lu,\"buf_bits\":%lu,\"rec_bits\":%lu,"
|
||||
"\"pct\":%lu,\"freq\":%.3f",
|
||||
(unsigned)m,
|
||||
modeStr[m < 4 ? m : 0],
|
||||
(unsigned long)capIdx,
|
||||
(unsigned long)(CAP_BUF_BYTES * 8),
|
||||
(unsigned long)capRecBits,
|
||||
(unsigned long)(capIdx * 100UL / (CAP_BUF_BYTES * 8)),
|
||||
(double)capFreq);
|
||||
|
||||
if (capMode == CapMode::RECORDED || capMode == CapMode::REPLAYING) {
|
||||
const String analysis = capAnalyze();
|
||||
json += "," + analysis.substring(1, analysis.length() - 1); // merge JSON fields
|
||||
if (capRecBits >= 100) {
|
||||
uint32_t ones = 0, transitions = 0;
|
||||
uint8_t prev = (capBuf[0] >> 0) & 1u;
|
||||
for (uint32_t i = 1; i < capRecBits; i++) {
|
||||
const uint8_t b = (capBuf[i >> 3] >> (i & 7)) & 1u;
|
||||
if (b) ones++;
|
||||
if (b != prev) { transitions++; prev = b; }
|
||||
}
|
||||
const uint32_t avgRL = (transitions > 0) ? (capRecBits / transitions) : capRecBits;
|
||||
n += snprintf(buf + n, sizeof(buf) - n,
|
||||
",\"dur_ms\":%lu,\"transitions\":%lu,\"est_bps\":%lu,\"duty_pct\":%lu",
|
||||
(unsigned long)(capRecBits * 1000 / CAP_SAMPLE_HZ),
|
||||
(unsigned long)transitions,
|
||||
(unsigned long)(avgRL > 0 ? CAP_SAMPLE_HZ / avgRL : 0),
|
||||
(unsigned long)(ones * 100UL / capRecBits));
|
||||
}
|
||||
}
|
||||
json += "}";
|
||||
server.send(200, "application/json; charset=utf-8", json);
|
||||
snprintf(buf + n, sizeof(buf) - n, "}");
|
||||
server.send(200, "application/json; charset=utf-8", buf);
|
||||
}
|
||||
|
||||
// Returns 256 data points (0-100 = % carrier-on) for waveform canvas rendering.
|
||||
// Static buffer: worst case = 256 * 4 chars ("100,") + 2 brackets + nul = 1026 bytes.
|
||||
static void handleCaptureWave() {
|
||||
if (!capRecBits) { server.send(200, "application/json", "[]"); return; }
|
||||
static char waveBuf[1280];
|
||||
const uint32_t N = 256;
|
||||
const uint32_t bpp = max(1u, capRecBits / N); // bits per point
|
||||
String json = "[";
|
||||
const uint32_t bpp = max(1u, capRecBits / N);
|
||||
int pos = 0;
|
||||
waveBuf[pos++] = '[';
|
||||
for (uint32_t p = 0; p < N; p++) {
|
||||
uint32_t ones = 0;
|
||||
const uint32_t start = p * bpp;
|
||||
@@ -1720,11 +1767,13 @@ static void handleCaptureWave() {
|
||||
for (uint32_t b = start; b < end; b++) {
|
||||
ones += (capBuf[b >> 3] >> (b & 7)) & 1u;
|
||||
}
|
||||
json += String(bpp > 0 ? ones * 100 / bpp : 0);
|
||||
if (p < N - 1) json += ',';
|
||||
pos += snprintf(waveBuf + pos, sizeof(waveBuf) - pos, "%lu%s",
|
||||
(unsigned long)(bpp > 0 ? ones * 100 / bpp : 0),
|
||||
(p < N - 1) ? "," : "");
|
||||
}
|
||||
json += ']';
|
||||
server.send(200, "application/json", json);
|
||||
waveBuf[pos++] = ']';
|
||||
waveBuf[pos] = '\0';
|
||||
server.send(200, "application/json", waveBuf);
|
||||
}
|
||||
|
||||
static void handleNotFound() {
|
||||
|
||||
Reference in New Issue
Block a user