## Potential bugs / edge cases (firmware + UI) **Scope**: This file lists things that look *potentially* wrong, brittle, or surprising in the current codebase. None of these are confirmed failures on your hardware; they are review notes and future-hardening targets. --- ### 1. Capture / replay concurrency and timing - **Non-atomic shared counters between ISR and main loop** - `capIdx`, `capTransitions`, and `capLongRuns` are updated in `capRecordISR()` and read from the main loop / OLED / HTTP handlers without any critical section. - On ESP32, aligned 32‑bit loads/stores are usually atomic, but you can still observe off‑by‑one or partially updated values when reading while the ISR is running. - **Risk**: Displayed progress / bitrate / squelch metrics can be slightly wrong or jittery while recording. Functionally low‑risk, but it’s technically a race. - **~~Capture analysis first bit~~** — addressed: bit 0 is now included in the `ones` count. - **Bitrate assumptions vs real keyfob signals** - Capture is hard‑wired at `CAP_SAMPLE_HZ = 100000` (100 kHz) with a fixed 4‑second window. - Many car fobs run significantly faster than 10 kbit/s; very high data‑rate or very short packets can alias or barely fill the buffer before the squelch decides “signal caught”. - **Risk**: Certain high‑rate or exotic protocols may be captured with degraded timing or not detected by the long‑run squelch at all. --- ### 2. Interaction between jamming and capture / replay - **State restoration depends on `capPrevJamming` flag only** - `startCapture()` and `startReplay()` store `capPrevJamming = jammingEnabled` before calling `stopJamming()`, and `stopCapture()` uses that flag to restart jamming. - If another part of the system toggles `jammingEnabled` while capture is in progress (e.g. a web API call), `capPrevJamming` can become stale and the final jamming state after STOP may not match user expectations. - **Risk**: Rare UX bug where jamming ends up on/off opposite to what the web UI last requested when you mix capture/replay and manual toggles aggressively. - **GDO0 direction flips vs noise ISR** - `noiseISR()` drives both `CC1101_*_GDO0` pins every 20 µs during jamming, but capture/replay reconfigures those same pins as input/output for bit‑banging. - The code tries to prevent overlap by calling `stopJamming()` before touching the capture timer and then restoring pin direction, but this depends on `stopJamming()` always fully killing `s_noiseTimer` first. - **Risk**: If `stopJamming()` ever early‑returns or is modified later, you could get noise ISR writes colliding with capture/replay GPIO direction changes. Right now it looks correct, but it’s a fragile area to touch. --- ### 3. Fast sweep / VCO cache (removed in current firmware) Jamming is **fixed dual-carrier** (315 MHz + 433.92 MHz); `buildSweepTable`, `tickSweepFast`, and sweep tables are **not present**. The web “Apply Sweep” path still stores dwell/steps/span in NVS but does **not** affect jam TX. If sweeps are **reintroduced**, restore bounded step counts, `setFrequency` error checks, and VCO cal timeouts. --- ### 3b. CC1101 (TI SWRS061) — fixed-carrier notes - **LO / PLL**: Channel frequency is `FREQ2:FREQ1:FREQ0` after RadioLib `setFrequency`. Large temperature swing can drift the VCO vs a trim-heavy fob RX; optional future work is periodic `SCAL` or re-init (not done here). - **DEVIAT**: FM deviation for the LFSR async TX path is `JAM_DEV_KHZ_R1_NARROW` vs `JAM_DEV_KHZ_R2_WIDE` in `config.h`. If 315 MHz jam feels weak, raise R1 deviation toward R2. - **PATABLE**: Explicit burst PATABLE is used for **OOK replay** only; jam uses direct async + RadioLib defaults unless you add more SPI. - **SPI / GDO0**: Noise ISR only toggles GDO0 GPIOs; register SPI stays on the main thread — keep it that way when editing `stopJamming` / capture. --- ### 4. Timer usage and ISR safety - **Multiple hardware timers, no central ownership tracking** - Timer 2 is used for `noiseISR()`; timer 3 is used for `capRecordISR()`/`capReplayISR()`. Each `*_Start()` tears down and re‑creates its timer instance. - There is no global check to prevent future code from reusing the same timer IDs for something else; reuse would race with the existing teardown, especially if done from another task. - **Risk**: Currently safe as long as no new timers are introduced. Future features must avoid timer IDs 2 and 3 or add a small timer allocation helper. - **GPIO driver calls from ISRs** - `noiseISR()` and `capReplayISR()` call `gpio_set_level()` directly from IRAM ISRs. - On ESP32 the GPIO driver is generally ISR‑safe and IRAM‑resident, but this depends on IDF/Arduino internals. If the platform evolves or gets misconfigured (e.g. non‑IRAM gpio functions), these ISRs could start hitting flash and cause WDT resets under load. - **Risk**: Low on current IDF/Arduino, but this is one of the first places to check if you ever see random WDT resets under heavy jamming. --- ### 5. Web UI / HTTP handlers - **Log text is built with `String` and served as a big blob** - `getLogsText()` builds a single large `String` (`reserve(4096)`) and returns it for `/log` downloads. - On its own this is fine, but if log lines become much longer than expected or you ever increase `LOG_LINES`, the 4 KB reserve may under‑estimate and cause heap fragmentation again. - **Risk**: Potential future fragmentation if log length grows substantially; currently appears safe with short 100‑line logs. - **AP password is a hard‑coded weak string** - `WIFI_AP_PASS` is literally `"password"`. - **Risk**: Anyone in RF range can connect to the AP and control the jammer UI. For a lab toy this is fine; for anything outside a controlled environment this is a security hole. --- ### 6. OLED and rotary encoder - **Encoder ISR uses `digitalRead()` twice per detent** - `encISR()` calls `digitalRead(ENC_CLK_PIN)` and `digitalRead(ENC_DT_PIN)` directly; those are relatively slow, and they’re called from an ISR. - **Risk**: Under high interrupt storm (very fast dial spins) you could see jitter or missed ticks. This is more of a performance nit than a hard bug, but it’s the weak point of the input path. - **Notifications can delay page auto‑advance longer than expected** - `oledNotify()` sets `oledPageMs = notifEnd`, and the auto‑advance check uses `now > notifEnd && now - oledPageMs >= 8000`. - After a long notification (e.g. multiple back‑to‑back events), page cycling waits an extra 8 seconds after the last notification before moving again. - **Risk**: UX oddity where pages seem “stuck” on status after a burst of notifications; not a functional bug. --- ### 7. Miscellaneous assumptions - **Radio init retries are hard‑coded to 3 attempts** - `startJamming()` retries `radio.begin(...)` up to 3 times with 50 ms between attempts. - **Risk**: If a board needs a longer warm‑up (slow 3V3 rail, bad caps), you might hit a permanent “Init failed” state when a slightly longer retry or backoff would have recovered. - **Power math in health screen clips at 9999 mW** - Effective mW is computed from dBm and then clamped at 9999: any higher values silently display `XXXX / 9999mW` style numbers. - **Risk**: Pure cosmetic; if you ever configured absurd gain values in the UI the display no longer reflects the math exactly.