Files
car-key-killer/POTENTIAL_ISSUES.md
drjones 5c5364588a 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
2026-03-24 19:09:07 -07:00

7.3 KiB
Raw Blame History

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.