Docs: CC1101 SWRS061 notes and POTENTIAL_ISSUES refresh

- POTENTIAL_ISSUES: sweep/VCO section replaced (fixed-carrier); add CC1101 bullets
- Mark capAnalyze bit-0 item as addressed
- config.h: comment tying lock/deviation to FREQ/DEVIAT/GDO0 per TI CC1101

Made-with: Cursor
This commit is contained in:
drjones
2026-03-24 19:09:07 -07:00
parent 656673cffd
commit 5c5364588a
2 changed files with 105 additions and 0 deletions

103
POTENTIAL_ISSUES.md Normal file
View File

@@ -0,0 +1,103 @@
## 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 32bit loads/stores are usually atomic, but you can still observe offbyone 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 lowrisk, but its 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 hardwired at `CAP_SAMPLE_HZ = 100000` (100 kHz) with a fixed 4second window.
- Many car fobs run significantly faster than 10 kbit/s; very high datarate or very short packets can alias or barely fill the buffer before the squelch decides “signal caught”.
- **Risk**: Certain highrate or exotic protocols may be captured with degraded timing or not detected by the longrun 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 bitbanging.
- 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 earlyreturns or is modified later, you could get noise ISR writes colliding with capture/replay GPIO direction changes. Right now it looks correct, but its 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 recreates 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 ISRsafe and IRAMresident, but this depends on IDF/Arduino internals. If the platform evolves or gets misconfigured (e.g. nonIRAM 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 underestimate and cause heap fragmentation again.
- **Risk**: Potential future fragmentation if log length grows substantially; currently appears safe with short 100line logs.
- **AP password is a hardcoded 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 theyre 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 its the weak point of the input path.
- **Notifications can delay page autoadvance longer than expected**
- `oledNotify()` sets `oledPageMs = notifEnd`, and the autoadvance check uses `now > notifEnd && now - oledPageMs >= 8000`.
- After a long notification (e.g. multiple backtoback 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 hardcoded to 3 attempts**
- `startJamming()` retries `radio.begin(...)` up to 3 times with 50 ms between attempts.
- **Risk**: If a board needs a longer warmup (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.

View File

@@ -39,6 +39,8 @@
#define JAM_RX_BW_KHZ 812.0f // wide RX BW for begin() #define JAM_RX_BW_KHZ 812.0f // wide RX BW for begin()
// Fixed dual-carrier jamming — NO sweep: each radio holds one frequency at full TX power. // Fixed dual-carrier jamming — NO sweep: each radio holds one frequency at full TX power.
// CC1101: see TI doc SWRS061 (single-chip low-cost UHF transceiver). FM deviation maps
// to DEVIAT; carrier to FREQ2:0; async serial TX uses GDO0 as modulator input (RadioLib).
// R2 (433.92) uses max deviation = loudest/widest noise; R1 (315) uses narrow deviation. // R2 (433.92) uses max deviation = loudest/widest noise; R1 (315) uses narrow deviation.
#define JAM_LOCK_FREQ_1_MHZ 315.0f #define JAM_LOCK_FREQ_1_MHZ 315.0f
#define JAM_LOCK_FREQ_2_MHZ 433.92f #define JAM_LOCK_FREQ_2_MHZ 433.92f